@nxgt/material 3.67.0 → 3.70.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 +38 -0
- package/README.md +6 -4
- package/dist/{alert-dialog-BgQDxcYh.js → alert-dialog-CBkWG32g.js} +1 -1
- package/dist/{async-slice-BCZyWgGr.js → async-slice-CNZ3N55P.js} +1 -1
- package/dist/{button-2de_lrix.js → button-D3qzqDY_.js} +1 -1
- package/dist/charts.js +3 -3
- package/dist/{chip-Cs0uEjpu.js → chip-CMTe4sQd.js} +2 -2
- package/dist/{code-editor-BYhBAfcc.js → code-editor-Cs7mz1J-.js} +5 -5
- package/dist/code-editor.js +5 -5
- package/dist/{code-language-items-xhmbF6wA.js → code-language-items-D_QMgjVy.js} +2 -2
- package/dist/{components-BRWaX4Xk.js → components-BbWcozcj.js} +18104 -17600
- package/dist/components.js +28 -28
- package/dist/{dialog-Co5oksU1.js → dialog-B3BSdRci.js} +4 -4
- package/dist/dnd.js +1 -1
- package/dist/{dropdown-menu-DdzyvN76.js → dropdown-menu-BaupOWgS.js} +1 -1
- package/dist/editor.js +661 -661
- package/dist/{empty-state-CQ0CV0kA.js → empty-state-B2Um5UpI.js} +2 -2
- package/dist/{field-blur-B2y-bNYi.js → field-blur-1XLyxRPr.js} +82 -82
- package/dist/{field-blur-context-BZtwcPUV.js → field-blur-context-XNvCDnMT.js} +1 -1
- package/dist/{hooks-DjuYsY-j.js → hooks-BU21Jpaa.js} +2 -2
- package/dist/hooks.js +2 -2
- package/dist/{hover-card-BeBVy6tB.js → hover-card-Bvm3svX9.js} +1 -1
- package/dist/i18n.js +3 -3
- package/dist/{icon-badge-DbYsbzwZ.js → icon-badge-wip9bfa2.js} +3 -3
- package/dist/{icon-button-Co6ttdv1.js → icon-button-QwIeBTdw.js} +1 -1
- package/dist/lib/components/ui/collections/filter/use-filter-field-config.d.ts +3 -3
- package/dist/lib/components/ui/fields/choice-list/choice-list-field.d.ts +13 -0
- package/dist/lib/components/ui/fields/choice-list/choice-list-form-field.d.ts +19 -0
- package/dist/lib/components/ui/fields/choice-list/choice-list-names.d.ts +19 -0
- package/dist/lib/components/ui/fields/choice-list/choice-list.utils.d.ts +27 -0
- package/dist/lib/components/ui/fields/choice-list/index.d.ts +3 -0
- package/dist/lib/components/ui/fields/choice-list/partials/choice-mark.d.ts +18 -0
- package/dist/lib/components/ui/fields/choice-list/partials/choice-row.d.ts +19 -0
- package/dist/lib/components/ui/fields/choice-list/types.d.ts +44 -0
- package/dist/lib/components/ui/fields/contacts/placeholders.d.ts +1 -1
- package/dist/lib/components/ui/fields/country-field/index.d.ts +2 -0
- package/dist/lib/components/ui/fields/country-field/iso-countries.d.ts +2 -0
- package/dist/lib/components/ui/fields/index.d.ts +2 -0
- package/dist/lib/components/ui/fields/phone-field/index.d.ts +1 -1
- package/dist/lib/components/ui/fields/phone-field/parse-phone.d.ts +2 -0
- package/dist/lib/components/ui/fields/phone-field/phone-field.d.ts +8 -1
- package/dist/lib/components/ui/fields/phone-field/phone-form-field.d.ts +8 -1
- package/dist/lib/components/ui/fields/phone-field/use-phone-value.d.ts +2 -1
- package/dist/lib/components/ui/fields/postal-address/utils.d.ts +15 -0
- package/dist/lib/components/ui/fields/quantity-field/index.d.ts +3 -0
- package/dist/lib/components/ui/fields/quantity-field/quantity-field.d.ts +8 -0
- package/dist/lib/components/ui/fields/quantity-field/quantity-form-field.d.ts +8 -0
- package/dist/lib/components/ui/fields/quantity-field/quantity-row.d.ts +13 -0
- package/dist/lib/components/ui/fields/quantity-field/quantity-unit.d.ts +12 -0
- package/dist/lib/components/ui/fields/quantity-field/quantity-value.d.ts +9 -0
- package/dist/lib/components/ui/fields/quantity-field/types.d.ts +45 -0
- package/dist/lib/components/ui/media/upload/index.d.ts +1 -0
- package/dist/lib/components/ui/media/upload/network-upload-field/network-upload-field.d.ts +6 -1
- package/dist/lib/components/ui/media/upload/network-upload-field/network-upload-field.errors.d.ts +8 -0
- package/dist/lib/components/ui/media/upload/network-upload-field/network-upload-field.utils.d.ts +2 -7
- package/dist/lib/components/ui/media/upload/network-upload-field/use-start-upload.d.ts +7 -3
- package/dist/lib/components/ui/media/upload/network-upload-field/use-upload-item-handlers.d.ts +2 -1
- package/dist/lib/components/ui/media/upload/s3-upload-field/s3-upload-field.d.ts +2 -5
- package/dist/lib/components/ui/media/upload/s3-upload-field/s3-upload-field.utils.d.ts +26 -1
- package/dist/lib/components/ui/media/upload/upload-error.d.ts +33 -0
- package/dist/lib/components/ui/selection/autocomplete/autocomplete-enter.d.ts +18 -0
- package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-chip-input.d.ts +2 -1
- package/dist/lib/components/ui/selection/autocomplete/partials/autocomplete-text-input.d.ts +2 -1
- package/dist/lib/components/ui/selection/autocomplete/types.d.ts +3 -1
- package/dist/lib/components/ui/widgets/education/questions/partials/question-content.d.ts +1 -1
- package/dist/lib/components/ui/widgets/education/questions/question-text.d.ts +1 -1
- package/dist/lib/components/ui/widgets/education/questions/question.d.ts +1 -1
- package/dist/lib/components/ui/widgets/education/questions/types.d.ts +10 -0
- package/dist/lib/i18n/resources/en/en.d.ts +20 -0
- package/dist/lib/i18n/resources/en/material.json.d.ts +21 -1
- package/dist/lib/i18n/resources/fr/fr.d.ts +20 -0
- package/dist/lib/i18n/resources/fr/material.json.d.ts +21 -1
- package/dist/lib/i18n/resources/resources.d.ts +40 -0
- package/dist/lib/i18n/translator.d.ts +1 -1
- package/dist/lib.js +1 -1
- package/dist/main.js +42 -42
- package/dist/map.js +8 -8
- package/dist/material.css +1 -1
- package/dist/pdf-reader.js +1 -1
- package/dist/print.js +1 -1
- package/dist/{public-AXwv1h3v.js → public-BuGgOtAl.js} +9 -9
- package/dist/{rich-code-block-qSvkA5rI.js → rich-code-block-DdHqzzek.js} +5 -5
- package/dist/{sortable-provider-DPk-RVEU.js → sortable-provider-DMyaSfZZ.js} +2 -2
- package/dist/{spinner-PdSk7zEs.js → spinner-ByYdcukU.js} +1 -1
- package/dist/theme-picker.js +2 -2
- package/dist/{theme-toggle-CZQckLDd.js → theme-toggle-DB3aZZnO.js} +1 -1
- package/dist/{toolbar-items-BmNYNaRB.js → toolbar-items-DbzIKjo7.js} +3 -3
- package/dist/{translate-material-BlCQ6AW9.js → translate-material-Dh7MYkHT.js} +1 -1
- package/dist/{translator-ChpeKCO3.js → translator-Byhohi1S.js} +42 -2
- package/dist/{typography-C4JSRr2B.js → typography-rUf0mE5a.js} +1 -1
- package/dist/{use-chat-composer-OUa9akfM.js → use-chat-composer-uUUTRB3Z.js} +2401 -2343
- package/dist/{use-heading-spy-K-HUp4o_.js → use-heading-spy-CVzW2NM3.js} +4 -4
- package/dist/{use-material-translation-DkE9J5NE.js → use-material-translation-BAYyZOcH.js} +1 -1
- package/dist/{use-return-focus-KypnR7vv.js → use-return-focus-CQAuk9kh.js} +1 -1
- package/docs/components/form-fields.md +196 -3
- package/docs/components/media-and-files.md +55 -9
- package/docs/components/primitives.md +9 -14
- package/docs/components/rich-widgets.md +2 -1
- package/docs/components/selection-and-choice.md +10 -3
- package/docs/guides/forms.md +4 -3
- package/docs/previews/form-fields-choicelistfield.webp +0 -0
- package/docs/previews/form-fields-quantityfield.webp +0 -0
- package/docs/roadmap.md +5 -2
- package/docs/troubleshooting.md +11 -4
- package/package.json +1 -1
|
@@ -3,12 +3,13 @@ type Options = {
|
|
|
3
3
|
defaultValue: string | null;
|
|
4
4
|
defaultCountry: string;
|
|
5
5
|
onChange?: (value: string | null) => void;
|
|
6
|
+
onValidityChange?: (incomplete: boolean) => void;
|
|
6
7
|
};
|
|
7
8
|
/**
|
|
8
9
|
* The country and the national digits a phone field shows, and the E.164
|
|
9
10
|
* number it emits, controlled or not.
|
|
10
11
|
*/
|
|
11
|
-
export declare function usePhoneValue({ value, defaultValue, defaultCountry, onChange, }: Options): {
|
|
12
|
+
export declare function usePhoneValue({ value, defaultValue, defaultCountry, onChange, onValidityChange, }: Options): {
|
|
12
13
|
iso: string;
|
|
13
14
|
draft: string;
|
|
14
15
|
input: (raw: string) => void;
|
|
@@ -2,3 +2,18 @@ import { PostalAddressFieldValue } from './models.js';
|
|
|
2
2
|
export declare function isPostalAddressEmpty(data?: PostalAddressFieldValue | null): boolean;
|
|
3
3
|
export declare function postalAddressCountryName(iso?: string | null, language?: string): string;
|
|
4
4
|
export declare function cityLine(data?: PostalAddressFieldValue | null): string;
|
|
5
|
+
export type FormatPostalAddressOptions = {
|
|
6
|
+
/** The language of the country name (default `'en'`). */
|
|
7
|
+
language?: string;
|
|
8
|
+
/** Between the parts (default `', '`). */
|
|
9
|
+
separator?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* One line for a read view: street, city line, country name. The city line is
|
|
13
|
+
* "74000 Annecy" or, for GB, US, CA, IE, AU, NZ and IN, "London SW1A 2AA";
|
|
14
|
+
* the region follows the city. Empty parts are skipped and the country is
|
|
15
|
+
* named like `postalAddressCountryName`.
|
|
16
|
+
* @example formatPostalAddress({ street: '12 rue du Lac', postalCode: '74000', locality: 'Annecy', country: 'FR' }, { language: 'fr' })
|
|
17
|
+
* // "12 rue du Lac, 74000 Annecy, France"
|
|
18
|
+
*/
|
|
19
|
+
export declare function formatPostalAddress(address?: PostalAddressFieldValue | null, { language, separator }?: FormatPostalAddressOptions): string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { QuantityFieldProps } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* One labelled field holding an amount and its unit: a group named by the one
|
|
4
|
+
* label, a NumberField's number (locale decimal, min, max, step, steppers named
|
|
5
|
+
* after the field), a unit picker (or a typed unit), and one message under the
|
|
6
|
+
* pair. The value is `{ value, unit }`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function QuantityField({ label, helperText, className, value, defaultValue, onChange, unitLabel, 'aria-label': ariaLabel, ...props }: QuantityFieldProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { UseControllerProps } from 'react-hook-form';
|
|
2
|
+
import { QuantityFieldProps } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* A QuantityField wired to React Hook Form. The field's value is
|
|
5
|
+
* `{ value, unit }`; an error on the whole field, on `<name>.value` or on
|
|
6
|
+
* `<name>.unit` shows as the one message under the pair.
|
|
7
|
+
*/
|
|
8
|
+
export declare function QuantityFormField<T extends Record<string, any>>({ control, name, ...props }: Pick<UseControllerProps<T>, 'control' | 'name'> & Omit<QuantityFieldProps, 'name' | 'value' | 'onChange' | 'onBlur'>): import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { QuantityFieldProps, QuantityValue } from './types.js';
|
|
2
|
+
type QuantityRowProps = Pick<QuantityFieldProps, 'name' | 'id' | 'placeholder' | 'onBlur' | 'min' | 'max' | 'step' | 'hideSteppers' | 'units' | 'allowCustomUnit' | 'unitPlaceholder' | 'disabled' | 'required' | 'error' | 'ref'> & {
|
|
3
|
+
current: QuantityValue;
|
|
4
|
+
change: (next: QuantityValue) => void;
|
|
5
|
+
labelId?: string;
|
|
6
|
+
ariaLabel?: string;
|
|
7
|
+
describedBy?: string;
|
|
8
|
+
unitName: string;
|
|
9
|
+
stepperName?: string;
|
|
10
|
+
};
|
|
11
|
+
/** The pair on one line: the number (NumberControl's row) shrinks, the unit keeps its width. */
|
|
12
|
+
export declare function QuantityRow({ name, current, change, labelId, ariaLabel, describedBy, unitName, stepperName, unitPlaceholder, units, allowCustomUnit, ...props }: QuantityRowProps): import("react").JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { QuantityFieldProps } from './types.js';
|
|
2
|
+
type QuantityUnitProps = Pick<QuantityFieldProps, 'units' | 'allowCustomUnit' | 'disabled' | 'required' | 'error'> & {
|
|
3
|
+
unit: string;
|
|
4
|
+
onUnit: (unit: string) => void;
|
|
5
|
+
name?: string;
|
|
6
|
+
label: string;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
describedBy?: string;
|
|
9
|
+
};
|
|
10
|
+
/** A quantity's unit: a picker over `units`, or a text input suggesting them. */
|
|
11
|
+
export declare function QuantityUnitPicker({ units, allowCustomUnit, disabled, required, error, unit, onUnit, name, label, placeholder, describedBy, }: QuantityUnitProps): import("react").JSX.Element;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { QuantityValue } from './types.js';
|
|
2
|
+
/** The value of a quantity nobody has filled in. */
|
|
3
|
+
export declare const EMPTY_QUANTITY: QuantityValue;
|
|
4
|
+
/**
|
|
5
|
+
* The one message of a quantity's React Hook Form error: its own, else the
|
|
6
|
+
* number's, else the unit's (a resolver reports `dose.value` and `dose.unit`
|
|
7
|
+
* as children of `dose`).
|
|
8
|
+
*/
|
|
9
|
+
export declare function quantityErrorMessage(error: unknown): string | undefined;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { FocusEventHandler, ReactNode, Ref } from 'react';
|
|
2
|
+
/** What a QuantityField holds: the amount (`null` while empty) and its unit. */
|
|
3
|
+
export type QuantityValue = {
|
|
4
|
+
value: number | null;
|
|
5
|
+
unit: string;
|
|
6
|
+
};
|
|
7
|
+
/** A unit the picker offers: `value` is stored, `label` is shown. */
|
|
8
|
+
export type QuantityUnit = {
|
|
9
|
+
value: string;
|
|
10
|
+
label: string;
|
|
11
|
+
};
|
|
12
|
+
export type QuantityFieldProps = {
|
|
13
|
+
/** The one label of the pair; it names the group. */
|
|
14
|
+
label?: ReactNode;
|
|
15
|
+
/** The one message under the pair; the group, the number and the unit are described by it. */
|
|
16
|
+
helperText?: ReactNode;
|
|
17
|
+
error?: boolean;
|
|
18
|
+
required?: boolean;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
className?: string;
|
|
21
|
+
/** Names the parts `<name>.value` and `<name>.unit` for a native form. */
|
|
22
|
+
name?: string;
|
|
23
|
+
id?: string;
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
value?: QuantityValue;
|
|
26
|
+
defaultValue?: QuantityValue;
|
|
27
|
+
onChange?: (value: QuantityValue) => void;
|
|
28
|
+
/** Fires when the number input loses focus. */
|
|
29
|
+
onBlur?: FocusEventHandler<HTMLInputElement>;
|
|
30
|
+
min?: number;
|
|
31
|
+
max?: number;
|
|
32
|
+
step?: number;
|
|
33
|
+
hideSteppers?: boolean;
|
|
34
|
+
/** The units offered. */
|
|
35
|
+
units?: QuantityUnit[];
|
|
36
|
+
/** The unit is typed; `units` become suggestions. Without it the unit is picked from `units`. */
|
|
37
|
+
allowCustomUnit?: boolean;
|
|
38
|
+
unitPlaceholder?: string;
|
|
39
|
+
/** Names the unit part. Defaults to "<label> unit". */
|
|
40
|
+
unitLabel?: string;
|
|
41
|
+
/** Names the group when `label` is not plain text. */
|
|
42
|
+
'aria-label'?: string;
|
|
43
|
+
/** Reaches the number input (React Hook Form's `field.ref`, `setFocus`). */
|
|
44
|
+
ref?: Ref<HTMLInputElement>;
|
|
45
|
+
};
|
|
@@ -7,7 +7,12 @@ export type NetworkUploadFieldProps = {
|
|
|
7
7
|
accept?: string;
|
|
8
8
|
maxSize?: number;
|
|
9
9
|
maxFiles?: number;
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Sends the file and resolves with the value to commit. `signal` aborts
|
|
12
|
+
* when the row is removed or the field unmounts: pass it to the request
|
|
13
|
+
* (`fetch`, `putFileWithProgress`) so a cancelled upload never lands.
|
|
14
|
+
*/
|
|
15
|
+
onUpload: (file: File, onProgress?: (percent: number) => void, signal?: AbortSignal) => Promise<string>;
|
|
11
16
|
onRemove?: (value: string) => void | Promise<void>;
|
|
12
17
|
onError?: (file: File, error: unknown) => void;
|
|
13
18
|
/**
|
package/dist/lib/components/ui/media/upload/network-upload-field/network-upload-field.errors.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MaterialLocaleKey } from '../../../../../i18n/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Translates an upload message: `useMaterialTranslation()`'s `t` fits. Without
|
|
4
|
+
* one, the English fallback is formatted.
|
|
5
|
+
*/
|
|
6
|
+
export type UploadMessageTranslate = (key: MaterialLocaleKey, fallback: string, values?: Record<string, string | number>) => string;
|
|
7
|
+
export declare const inEnglish: UploadMessageTranslate;
|
|
8
|
+
export declare function getUploadErrorMessage(error: unknown, t?: UploadMessageTranslate): string;
|
package/dist/lib/components/ui/media/upload/network-upload-field/network-upload-field.utils.d.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
/**
|
|
3
|
-
* Translates an upload message: `useMaterialTranslation()`'s `t` fits. Without
|
|
4
|
-
* one, the English fallback is formatted.
|
|
5
|
-
*/
|
|
6
|
-
export type UploadMessageTranslate = (key: MaterialLocaleKey, fallback: string, values?: Record<string, string | number>) => string;
|
|
1
|
+
import { UploadMessageTranslate } from './network-upload-field.errors.js';
|
|
7
2
|
export type FileValidationError = 'accept' | 'max-size' | 'max-files';
|
|
8
3
|
export type ValidateFileOptions = {
|
|
9
4
|
accept?: string;
|
|
@@ -16,7 +11,6 @@ export declare function validateFile(file: File, options: ValidateFileOptions):
|
|
|
16
11
|
export declare function getValidationMessage(error: FileValidationError, options: ValidateFileOptions, t?: UploadMessageTranslate): string;
|
|
17
12
|
export declare function createFileId(): string;
|
|
18
13
|
export declare function filenameFromValue(value: string): string;
|
|
19
|
-
export declare function getUploadErrorMessage(error: unknown, t?: UploadMessageTranslate): string;
|
|
20
14
|
/**
|
|
21
15
|
* Whether a stored value looks like an image, judged by extension.
|
|
22
16
|
*
|
|
@@ -43,3 +37,4 @@ export declare function isDisplayableSrc(value: string): boolean;
|
|
|
43
37
|
* image is enough.
|
|
44
38
|
*/
|
|
45
39
|
export declare function resolvePreviewUrl(value: string, resolveUrl?: (value: string) => string | null | undefined): string | undefined;
|
|
40
|
+
export { getUploadErrorMessage, type UploadMessageTranslate, } from './network-upload-field.errors.js';
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { RefObject } from 'react';
|
|
2
2
|
import { UploadItemActions } from './network-upload-field.slice.js';
|
|
3
3
|
import { UploadMessageTranslate } from './network-upload-field.utils.js';
|
|
4
|
+
type UploadFn = (file: File, onProgress?: (percent: number) => void, signal?: AbortSignal) => Promise<string>;
|
|
4
5
|
/**
|
|
5
6
|
* Runs `onUpload` for one local item: its progress, then the committed value
|
|
6
|
-
* or the error. An item removed meanwhile (`removedIdsRef`)
|
|
7
|
+
* or the error. An item removed meanwhile (`removedIdsRef`) has its request
|
|
8
|
+
* aborted (`controllersRef`) and is left alone: neither an error nor a value.
|
|
7
9
|
*/
|
|
8
|
-
export declare function useStartUpload({ onUpload, onError, actions, removedIdsRef, commitUploaded, t, }: {
|
|
9
|
-
onUpload:
|
|
10
|
+
export declare function useStartUpload({ onUpload, onError, actions, removedIdsRef, controllersRef, commitUploaded, t, }: {
|
|
11
|
+
onUpload: UploadFn;
|
|
10
12
|
onError?: (file: File, error: unknown) => void;
|
|
11
13
|
actions: UploadItemActions;
|
|
12
14
|
removedIdsRef: RefObject<Set<string>>;
|
|
15
|
+
controllersRef: RefObject<Map<string, AbortController>>;
|
|
13
16
|
commitUploaded: (value: string, file: File, replacesValue?: string) => void;
|
|
14
17
|
t: UploadMessageTranslate;
|
|
15
18
|
}): (uploadId: string, file: File, replacesValue?: string) => void;
|
|
19
|
+
export {};
|
package/dist/lib/components/ui/media/upload/network-upload-field/use-upload-item-handlers.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type UploadItemHandlerDeps = {
|
|
|
10
10
|
onError?: (file: File, error: unknown) => void;
|
|
11
11
|
actions: UploadItemActions;
|
|
12
12
|
removedIdsRef: RefObject<Set<string>>;
|
|
13
|
+
controllersRef: RefObject<Map<string, AbortController>>;
|
|
13
14
|
startUpload: (uploadId: string, file: File, replacesValue?: string) => void;
|
|
14
15
|
addLocalItem: (file: File, options?: {
|
|
15
16
|
replacesValue?: string;
|
|
@@ -18,7 +19,7 @@ export type UploadItemHandlerDeps = {
|
|
|
18
19
|
t: UploadMessageTranslate;
|
|
19
20
|
};
|
|
20
21
|
/** What a row's Replace, Retry and Remove do, for a local item or a value. */
|
|
21
|
-
export declare function useUploadItemHandlers({ disabled, accept, maxSize, onRemove, onError, actions, removedIdsRef, startUpload, addLocalItem, commitRemove, t, }: UploadItemHandlerDeps): {
|
|
22
|
+
export declare function useUploadItemHandlers({ disabled, accept, maxSize, onRemove, onError, actions, removedIdsRef, controllersRef, startUpload, addLocalItem, commitRemove, t, }: UploadItemHandlerDeps): {
|
|
22
23
|
handleItemReplace: (item: NetworkUploadDisplayItem, file: File) => void;
|
|
23
24
|
handleItemRetry: (item: NetworkUploadDisplayItem) => void;
|
|
24
25
|
handleItemRemove: (item: NetworkUploadDisplayItem) => void;
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { DistributiveOmit } from '../../../../../lib/distributive-omit.js';
|
|
2
2
|
import { NetworkUploadFieldProps } from '../network-upload-field/network-upload-field.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
publicUrl: string;
|
|
6
|
-
headers?: Record<string, string>;
|
|
7
|
-
};
|
|
3
|
+
import { PresignedUploadTarget } from './s3-upload-field.utils.js';
|
|
4
|
+
export type { PresignedPostTarget, PresignedPutTarget, PresignedUploadTarget, } from './s3-upload-field.utils.js';
|
|
8
5
|
export type S3UploadFieldProps = DistributiveOmit<NetworkUploadFieldProps, 'onUpload'> & {
|
|
9
6
|
getPresignedUrl: (file: File) => Promise<PresignedUploadTarget>;
|
|
10
7
|
};
|
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
export type PutFileWithProgressOptions = {
|
|
2
2
|
headers?: Record<string, string>;
|
|
3
3
|
onProgress?: (percent: number) => void;
|
|
4
|
+
/** Aborts the request (`xhr.abort()`); the promise then rejects with an `UploadError` of kind `aborted` (named `AbortError`). */
|
|
5
|
+
signal?: AbortSignal;
|
|
4
6
|
};
|
|
5
|
-
|
|
7
|
+
/** A presigned PUT: the file is the request body. */
|
|
8
|
+
export type PresignedPutTarget = {
|
|
9
|
+
method?: 'PUT';
|
|
10
|
+
uploadUrl: string;
|
|
11
|
+
publicUrl: string;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* A presigned POST policy: `fields` are sent first, in order, then the file
|
|
16
|
+
* as the last part, named `file`. `value` is what the field stores (the key).
|
|
17
|
+
*/
|
|
18
|
+
export type PresignedPostTarget = {
|
|
19
|
+
method: 'POST';
|
|
20
|
+
url: string;
|
|
21
|
+
fields: Record<string, string>;
|
|
22
|
+
value: string;
|
|
23
|
+
};
|
|
24
|
+
export type PresignedUploadTarget = PresignedPutTarget | PresignedPostTarget;
|
|
25
|
+
/** Fields in insertion order, then the file last: S3 ignores what follows `file`. */
|
|
26
|
+
export declare function buildPostFormData(fields: Record<string, string>, file: File): FormData;
|
|
27
|
+
export declare function putFileWithProgress(url: string, file: File, { headers, onProgress, signal }?: PutFileWithProgressOptions): Promise<void>;
|
|
28
|
+
export declare function postFileWithProgress(url: string, fields: Record<string, string>, file: File, { onProgress, signal, }?: Pick<PutFileWithProgressOptions, 'onProgress' | 'signal'>): Promise<void>;
|
|
29
|
+
/** Sends `file` to a PUT or POST target and resolves with the value to store. */
|
|
30
|
+
export declare function uploadToTarget(target: PresignedUploadTarget, file: File, onProgress?: (percent: number) => void, signal?: AbortSignal): Promise<string>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why a transport-level upload failed. The S3 helpers (`putFileWithProgress`,
|
|
3
|
+
* `postFileWithProgress`, `uploadToTarget`) reject with an `UploadError`, so a
|
|
4
|
+
* field can word the failure in the active language.
|
|
5
|
+
*
|
|
6
|
+
* - `status`: the storage answered a non-2xx `status`; `code` is the S3 error
|
|
7
|
+
* code (`EntityTooLarge`) when the XML body carries one.
|
|
8
|
+
* - `network`: the request never completed (CORS, offline, blocked by CSP).
|
|
9
|
+
* - `aborted`: the request was cancelled through its `AbortSignal`. The error's
|
|
10
|
+
* `name` is `AbortError`.
|
|
11
|
+
*
|
|
12
|
+
* `message` stays English, for logs and for code that reads it. An abort is an
|
|
13
|
+
* `UploadError` named `AbortError`, no longer a `DOMException`: test
|
|
14
|
+
* `error.name === 'AbortError'` or `error.kind === 'aborted'`, not `instanceof
|
|
15
|
+
* DOMException`. Across two copies of the package, use `isUploadError(e)`
|
|
16
|
+
* rather than `instanceof`.
|
|
17
|
+
*/
|
|
18
|
+
export type UploadErrorKind = 'status' | 'network' | 'aborted';
|
|
19
|
+
export declare class UploadError extends Error {
|
|
20
|
+
/** Brand read by `isUploadError`, so a copy from another bundle is recognised. */
|
|
21
|
+
readonly isUploadError = true;
|
|
22
|
+
readonly kind: UploadErrorKind;
|
|
23
|
+
/** The HTTP status, for `kind: 'status'`. */
|
|
24
|
+
readonly status?: number;
|
|
25
|
+
/** The S3 error code, for `kind: 'status'` when the body carried one. */
|
|
26
|
+
readonly code?: string;
|
|
27
|
+
constructor(kind: UploadErrorKind, options?: {
|
|
28
|
+
status?: number;
|
|
29
|
+
code?: string;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Structural guard: an `UploadError`, even one made by another copy of the package. */
|
|
33
|
+
export declare function isUploadError(error: unknown): error is UploadError;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { KeyboardEvent } from 'react';
|
|
2
|
+
type Enter = {
|
|
3
|
+
freeSolo?: boolean;
|
|
4
|
+
/** Typed text that matches no option: the "Add" button shows. */
|
|
5
|
+
showAddButton: boolean;
|
|
6
|
+
open: boolean;
|
|
7
|
+
onAdd: () => void;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Enter in the input, as the WAI-ARIA combobox pattern has it: Enter acts on
|
|
11
|
+
* the list, so it never submits the surrounding form while it has something to
|
|
12
|
+
* do. A highlighted option is picked (Base UI), else a `freeSolo` text with no
|
|
13
|
+
* match is added; either way, as with an open list, the default action (the
|
|
14
|
+
* form's implicit submission) is cancelled. Enter on a closed list, or with
|
|
15
|
+
* nothing typed, still submits as a plain input does.
|
|
16
|
+
*/
|
|
17
|
+
export declare function enterHandler({ freeSolo, showAddButton, open, onAdd }: Enter): (event: KeyboardEvent<HTMLInputElement>) => void;
|
|
18
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RefObject } from 'react';
|
|
2
|
-
import { AutocompleteOption, AutocompleteProps } from '../types.js';
|
|
2
|
+
import { AutocompleteChipsProps, AutocompleteOption, AutocompleteProps } from '../types.js';
|
|
3
3
|
/** The chips box: a chip for each selected option, then the input. */
|
|
4
4
|
export declare function AutocompleteChipInput<T extends AutocompleteOption>({ id, labelId, helperId, values, getOptionValue, getOptionLabel, ...props }: AutocompleteProps<T> & {
|
|
5
5
|
id: string;
|
|
@@ -8,6 +8,7 @@ export declare function AutocompleteChipInput<T extends AutocompleteOption>({ id
|
|
|
8
8
|
values: string[];
|
|
9
9
|
anchor: RefObject<HTMLDivElement | null>;
|
|
10
10
|
onRemove: (value: string) => void;
|
|
11
|
+
onInputKeyDown?: AutocompleteChipsProps<T>['onInputKeyDown'];
|
|
11
12
|
getOptionValue: (option: T) => string;
|
|
12
13
|
getOptionLabel: (option: T) => string;
|
|
13
14
|
}): import("react").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RefObject } from 'react';
|
|
2
|
-
import { AutocompleteOption, AutocompleteProps } from '../types.js';
|
|
2
|
+
import { AutocompleteChipsProps, AutocompleteOption, AutocompleteProps } from '../types.js';
|
|
3
3
|
/**
|
|
4
4
|
* `display="text"`: the single value is the input's own text, in the field
|
|
5
5
|
* box, with the clear button and the chevron at its end. The text is selected
|
|
@@ -12,4 +12,5 @@ export declare function AutocompleteTextInput<T extends AutocompleteOption>({ id
|
|
|
12
12
|
helperId: string;
|
|
13
13
|
/** The option picked, for its avatar before the text. */
|
|
14
14
|
selected: T | null;
|
|
15
|
+
onInputKeyDown?: AutocompleteChipsProps<T>['onInputKeyDown'];
|
|
15
16
|
}): import("react").JSX.Element;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FocusEvent, ReactNode, RefObject } from 'react';
|
|
1
|
+
import { FocusEvent, KeyboardEvent, ReactNode, RefObject } from 'react';
|
|
2
2
|
import { InputGroupFieldProps } from '../../fields/input-group-field/input-group-field.js';
|
|
3
3
|
export type AutocompleteOption = {
|
|
4
4
|
value: string;
|
|
@@ -111,6 +111,8 @@ export type AutocompleteChipsProps<T extends AutocompleteOption> = AutocompleteP
|
|
|
111
111
|
selected?: T | null;
|
|
112
112
|
/** Focus left an element of the field (`text` display restores the label). */
|
|
113
113
|
onFieldBlur?: (event: FocusEvent<HTMLElement>) => void;
|
|
114
|
+
/** Enter in the input: picks or adds, and keeps the form from submitting. */
|
|
115
|
+
onInputKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
|
|
114
116
|
/** What the field's polite live region says about the list. */
|
|
115
117
|
announcement?: ReactNode;
|
|
116
118
|
/** The list, when it sits in the field's flow (in a dialog). */
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { QuestionProps } from '../types.js';
|
|
2
2
|
/** A question's fieldset: prompt, header, media, hint, the answer body and the feedback. */
|
|
3
|
-
export declare function QuestionContent({ question, value, onValueChange, revealed: revealedProp, verdict, points, feedback, disabled, readOnly, busy, renderCode, promptId: promptIdProp, variant, density, showShortcuts, number, showFeedback, }: QuestionProps): import("react").JSX.Element;
|
|
3
|
+
export declare function QuestionContent({ question, value, onValueChange, revealed: revealedProp, verdict, points, feedback, disabled, readOnly, busy, renderCode, promptId: promptIdProp, variant, density, showShortcuts, number, showFeedback: showFeedbackProp, preview, }: QuestionProps): import("react").JSX.Element;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { QuestionProps } from './types.js';
|
|
2
2
|
/** A typed answer: `text`, or `number` (a decimal keypad, an optional unit). */
|
|
3
|
-
export declare function QuestionText({ question, value, onValueChange, revealed, verdict, disabled, readOnly, busy, showFeedback, }: QuestionProps): import("react").JSX.Element;
|
|
3
|
+
export declare function QuestionText({ question, value, onValueChange, revealed, verdict, disabled, readOnly, busy, showFeedback, preview, }: QuestionProps): import("react").JSX.Element;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { QuestionProps } from './types.js';
|
|
2
|
-
export declare function QuestionCard({ question, value, onValueChange, revealed, verdict, points, feedback, disabled, readOnly, busy, renderCode, promptId, className, variant, density, showShortcuts, number, showFeedback, }: QuestionProps): import("react").JSX.Element;
|
|
2
|
+
export declare function QuestionCard({ question, value, onValueChange, revealed, verdict, points, feedback, disabled, readOnly, busy, renderCode, promptId, className, variant, density, showShortcuts, number, showFeedback, preview, }: QuestionProps): import("react").JSX.Element;
|
|
@@ -133,4 +133,14 @@ export type QuestionProps = {
|
|
|
133
133
|
number?: number;
|
|
134
134
|
/** The verdict banner once revealed: right or wrong, the correct answer. */
|
|
135
135
|
showFeedback?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* An author's preview: the card is read-only and shows the marks, the
|
|
138
|
+
* `question.correct` answer (the right options marked, the expected text or
|
|
139
|
+
* number filled in) and the `explanation`, with no verdict banner.
|
|
140
|
+
* `verdict` and `showFeedback` are always ignored in a preview; `value` is
|
|
141
|
+
* ignored when the question has a `correct`. An `open` or `code` question
|
|
142
|
+
* has none, so pass its reference answer as `value`. Never used for a
|
|
143
|
+
* learner's attempt.
|
|
144
|
+
*/
|
|
145
|
+
preview?: boolean;
|
|
136
146
|
};
|
|
@@ -130,6 +130,10 @@ export declare const en: {
|
|
|
130
130
|
remove: string;
|
|
131
131
|
"replace-named": string;
|
|
132
132
|
failed: string;
|
|
133
|
+
"error-status": string;
|
|
134
|
+
"error-status-code": string;
|
|
135
|
+
"error-network": string;
|
|
136
|
+
"error-aborted": string;
|
|
133
137
|
"invalid-type": string;
|
|
134
138
|
"too-large": string;
|
|
135
139
|
"max-files": string;
|
|
@@ -1318,6 +1322,10 @@ export declare const en: {
|
|
|
1318
1322
|
"decrease-named": string;
|
|
1319
1323
|
"increase-named": string;
|
|
1320
1324
|
};
|
|
1325
|
+
"quantity-field": {
|
|
1326
|
+
unit: string;
|
|
1327
|
+
"unit-named": string;
|
|
1328
|
+
};
|
|
1321
1329
|
"textarea-field": {
|
|
1322
1330
|
count: string;
|
|
1323
1331
|
};
|
|
@@ -1342,6 +1350,7 @@ export declare const en: {
|
|
|
1342
1350
|
placeholder: string;
|
|
1343
1351
|
search: string;
|
|
1344
1352
|
empty: string;
|
|
1353
|
+
incomplete: string;
|
|
1345
1354
|
};
|
|
1346
1355
|
"rating-field": {
|
|
1347
1356
|
star: string;
|
|
@@ -2191,6 +2200,17 @@ export declare const en: {
|
|
|
2191
2200
|
"saved-required": string;
|
|
2192
2201
|
done: string;
|
|
2193
2202
|
};
|
|
2203
|
+
"choice-list": {
|
|
2204
|
+
row: string;
|
|
2205
|
+
correct: string;
|
|
2206
|
+
"correct-named": string;
|
|
2207
|
+
"too-few": string;
|
|
2208
|
+
"too-many": string;
|
|
2209
|
+
"none-single": string;
|
|
2210
|
+
"none-multiple": string;
|
|
2211
|
+
blank: string;
|
|
2212
|
+
"text-required": string;
|
|
2213
|
+
};
|
|
2194
2214
|
"repeatable-group": {
|
|
2195
2215
|
row: string;
|
|
2196
2216
|
add: string;
|
|
@@ -120,6 +120,10 @@ declare const _default: {
|
|
|
120
120
|
"remove": "Remove",
|
|
121
121
|
"replace-named": "Replace {name}",
|
|
122
122
|
"failed": "Upload failed",
|
|
123
|
+
"error-status": "Upload failed with status {status}",
|
|
124
|
+
"error-status-code": "Upload failed with status {status} ({code})",
|
|
125
|
+
"error-network": "Network error during upload",
|
|
126
|
+
"error-aborted": "Upload cancelled",
|
|
123
127
|
"invalid-type": "File type not accepted",
|
|
124
128
|
"too-large": "File exceeds maximum size of {size}",
|
|
125
129
|
"max-files": "{count, plural, one {Maximum of # file reached} other {Maximum of # files reached}}"
|
|
@@ -1308,6 +1312,10 @@ declare const _default: {
|
|
|
1308
1312
|
"decrease-named": "Decrease {label}",
|
|
1309
1313
|
"increase-named": "Increase {label}"
|
|
1310
1314
|
},
|
|
1315
|
+
"quantity-field": {
|
|
1316
|
+
"unit": "Unit",
|
|
1317
|
+
"unit-named": "{label} unit"
|
|
1318
|
+
},
|
|
1311
1319
|
"textarea-field": {
|
|
1312
1320
|
"count": "{count}/{max}"
|
|
1313
1321
|
},
|
|
@@ -1331,7 +1339,8 @@ declare const _default: {
|
|
|
1331
1339
|
"country": "Country code",
|
|
1332
1340
|
"placeholder": "6 12 34 56 78",
|
|
1333
1341
|
"search": "Search country",
|
|
1334
|
-
"empty": "No country found"
|
|
1342
|
+
"empty": "No country found",
|
|
1343
|
+
"incomplete": "Incomplete number"
|
|
1335
1344
|
},
|
|
1336
1345
|
"rating-field": {
|
|
1337
1346
|
"star": "{value} of {max}"
|
|
@@ -2181,6 +2190,17 @@ declare const _default: {
|
|
|
2181
2190
|
"saved-required": "Tick the box once your codes are safe",
|
|
2182
2191
|
"done": "Done"
|
|
2183
2192
|
},
|
|
2193
|
+
"choice-list": {
|
|
2194
|
+
"row": "answer",
|
|
2195
|
+
"correct": "Correct",
|
|
2196
|
+
"correct-named": "Correct: {label} {index}",
|
|
2197
|
+
"too-few": "Add at least {min, plural, one {# {label}} other {# {label}s}}",
|
|
2198
|
+
"too-many": "Mark only one correct {label}",
|
|
2199
|
+
"none-single": "Mark the correct {label}",
|
|
2200
|
+
"none-multiple": "Mark at least one correct {label}",
|
|
2201
|
+
"blank": "Fill in every {label}",
|
|
2202
|
+
"text-required": "Enter the {label}"
|
|
2203
|
+
},
|
|
2184
2204
|
"repeatable-group": {
|
|
2185
2205
|
"row": "row",
|
|
2186
2206
|
"add": "Add {label}",
|
|
@@ -130,6 +130,10 @@ export declare const fr: {
|
|
|
130
130
|
remove: string;
|
|
131
131
|
"replace-named": string;
|
|
132
132
|
failed: string;
|
|
133
|
+
"error-status": string;
|
|
134
|
+
"error-status-code": string;
|
|
135
|
+
"error-network": string;
|
|
136
|
+
"error-aborted": string;
|
|
133
137
|
"invalid-type": string;
|
|
134
138
|
"too-large": string;
|
|
135
139
|
"max-files": string;
|
|
@@ -1318,6 +1322,10 @@ export declare const fr: {
|
|
|
1318
1322
|
"decrease-named": string;
|
|
1319
1323
|
"increase-named": string;
|
|
1320
1324
|
};
|
|
1325
|
+
"quantity-field": {
|
|
1326
|
+
unit: string;
|
|
1327
|
+
"unit-named": string;
|
|
1328
|
+
};
|
|
1321
1329
|
"textarea-field": {
|
|
1322
1330
|
count: string;
|
|
1323
1331
|
};
|
|
@@ -1342,6 +1350,7 @@ export declare const fr: {
|
|
|
1342
1350
|
placeholder: string;
|
|
1343
1351
|
search: string;
|
|
1344
1352
|
empty: string;
|
|
1353
|
+
incomplete: string;
|
|
1345
1354
|
};
|
|
1346
1355
|
"rating-field": {
|
|
1347
1356
|
star: string;
|
|
@@ -2191,6 +2200,17 @@ export declare const fr: {
|
|
|
2191
2200
|
"saved-required": string;
|
|
2192
2201
|
done: string;
|
|
2193
2202
|
};
|
|
2203
|
+
"choice-list": {
|
|
2204
|
+
row: string;
|
|
2205
|
+
correct: string;
|
|
2206
|
+
"correct-named": string;
|
|
2207
|
+
"too-few": string;
|
|
2208
|
+
"too-many": string;
|
|
2209
|
+
"none-single": string;
|
|
2210
|
+
"none-multiple": string;
|
|
2211
|
+
blank: string;
|
|
2212
|
+
"text-required": string;
|
|
2213
|
+
};
|
|
2194
2214
|
"repeatable-group": {
|
|
2195
2215
|
row: string;
|
|
2196
2216
|
add: string;
|
|
@@ -120,6 +120,10 @@ declare const _default: {
|
|
|
120
120
|
"remove": "Supprimer",
|
|
121
121
|
"replace-named": "Remplacer {name}",
|
|
122
122
|
"failed": "Échec de l'envoi",
|
|
123
|
+
"error-status": "Échec de l'envoi (statut {status})",
|
|
124
|
+
"error-status-code": "Échec de l'envoi (statut {status}, {code})",
|
|
125
|
+
"error-network": "Erreur réseau pendant l'envoi",
|
|
126
|
+
"error-aborted": "Envoi annulé",
|
|
123
127
|
"invalid-type": "Type de fichier non accepté",
|
|
124
128
|
"too-large": "Le fichier dépasse la taille maximale de {size}",
|
|
125
129
|
"max-files": "{count, plural, one {Maximum de # fichier atteint} other {Maximum de # fichiers atteint}}"
|
|
@@ -1308,6 +1312,10 @@ declare const _default: {
|
|
|
1308
1312
|
"decrease-named": "Diminuer {label}",
|
|
1309
1313
|
"increase-named": "Augmenter {label}"
|
|
1310
1314
|
},
|
|
1315
|
+
"quantity-field": {
|
|
1316
|
+
"unit": "Unité",
|
|
1317
|
+
"unit-named": "Unité de {label}"
|
|
1318
|
+
},
|
|
1311
1319
|
"textarea-field": {
|
|
1312
1320
|
"count": "{count}/{max}"
|
|
1313
1321
|
},
|
|
@@ -1331,7 +1339,8 @@ declare const _default: {
|
|
|
1331
1339
|
"country": "Indicatif pays",
|
|
1332
1340
|
"placeholder": "6 12 34 56 78",
|
|
1333
1341
|
"search": "Rechercher un pays",
|
|
1334
|
-
"empty": "Aucun pays trouvé"
|
|
1342
|
+
"empty": "Aucun pays trouvé",
|
|
1343
|
+
"incomplete": "Numéro incomplet"
|
|
1335
1344
|
},
|
|
1336
1345
|
"rating-field": {
|
|
1337
1346
|
"star": "{value} sur {max}"
|
|
@@ -2181,6 +2190,17 @@ declare const _default: {
|
|
|
2181
2190
|
"saved-required": "Cochez la case une fois vos codes en lieu sûr",
|
|
2182
2191
|
"done": "Terminé"
|
|
2183
2192
|
},
|
|
2193
|
+
"choice-list": {
|
|
2194
|
+
"row": "réponse",
|
|
2195
|
+
"correct": "Juste",
|
|
2196
|
+
"correct-named": "Juste : {label} {index}",
|
|
2197
|
+
"too-few": "Ajoutez au moins {min, plural, one {# {label}} other {# {label}s}}",
|
|
2198
|
+
"too-many": "Indiquez une seule {label} juste",
|
|
2199
|
+
"none-single": "Indiquez la {label} juste",
|
|
2200
|
+
"none-multiple": "Indiquez au moins une {label} juste",
|
|
2201
|
+
"blank": "Remplissez chaque {label}",
|
|
2202
|
+
"text-required": "Saisissez la {label}"
|
|
2203
|
+
},
|
|
2184
2204
|
"repeatable-group": {
|
|
2185
2205
|
"row": "ligne",
|
|
2186
2206
|
"add": "Ajouter une {label}",
|