@focus-reactive/payload-plugin-translator 0.9.2 → 0.10.1

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 (69) hide show
  1. package/README.md +25 -0
  2. package/dist/client/entities/translation/api/mutations/useQueueDocumentTranslation.d.ts +1 -1
  3. package/dist/client/entities/translation/ui/AutoTranslateMarker/AutoTranslateMarker.d.ts +3 -4
  4. package/dist/client/entities/translation/ui/AutoTranslateMarker/AutoTranslateMarker.js +8 -12
  5. package/dist/client/entities/translation/ui/AutoTranslateMarker/styles.module.scss +3 -12
  6. package/dist/client/features/collection-translation-form/model/schema.d.ts +3 -3
  7. package/dist/client/features/collection-translation-form/model/schema.js +8 -1
  8. package/dist/client/features/collection-translation-form/ui/CollectionTranslationForm.d.ts +3 -1
  9. package/dist/client/features/collection-translation-form/ui/CollectionTranslationForm.js +36 -6
  10. package/dist/client/features/translate-document-form/model/schema.d.ts +3 -3
  11. package/dist/client/features/translate-document-form/model/schema.js +8 -1
  12. package/dist/client/features/translate-document-form/ui/DocumentTranslationForm.d.ts +3 -1
  13. package/dist/client/features/translate-document-form/ui/DocumentTranslationForm.js +36 -6
  14. package/dist/client/shared/lib/forms/pruneSourceFromTarget.d.ts +7 -0
  15. package/dist/client/shared/lib/forms/pruneSourceFromTarget.js +14 -0
  16. package/dist/client/shared/ui/InfoPopover/InfoPopover.d.ts +21 -0
  17. package/dist/client/shared/ui/InfoPopover/InfoPopover.js +45 -0
  18. package/dist/client/shared/ui/InfoPopover/index.d.ts +1 -0
  19. package/dist/client/shared/ui/InfoPopover/index.js +3 -0
  20. package/dist/client/shared/ui/InfoPopover/styles.module.scss +49 -0
  21. package/dist/client/shared/ui/MultiSelect/MultiSelect.d.ts +40 -0
  22. package/dist/client/shared/ui/MultiSelect/MultiSelect.js +129 -0
  23. package/dist/client/shared/ui/MultiSelect/index.d.ts +2 -0
  24. package/dist/client/shared/ui/MultiSelect/index.js +3 -0
  25. package/dist/client/shared/ui/MultiSelect/multiSelectModel.d.ts +25 -0
  26. package/dist/client/shared/ui/MultiSelect/multiSelectModel.js +41 -0
  27. package/dist/client/shared/ui/MultiSelect/styles.module.scss +126 -0
  28. package/dist/client/shared/ui/form/FormCheckboxPublish/FormCheckboxPublish.js +4 -7
  29. package/dist/client/shared/ui/form/FormMultiSelect/FormMultiSelect.d.ts +22 -0
  30. package/dist/client/shared/ui/form/FormMultiSelect/FormMultiSelect.js +70 -0
  31. package/dist/client/shared/ui/form/FormMultiSelect/index.d.ts +1 -0
  32. package/dist/client/shared/ui/form/FormMultiSelect/index.js +3 -0
  33. package/dist/client/shared/ui/form/FormSelectStrategy/FormSelectStrategy.js +4 -7
  34. package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.d.ts +3 -1
  35. package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.export.d.ts +3 -1
  36. package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.export.js +5 -2
  37. package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.js +8 -3
  38. package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.server.d.ts +2 -0
  39. package/dist/client/widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.server.js +2 -1
  40. package/dist/client/widgets/translate-document/ui/TranslateDocument.d.ts +3 -1
  41. package/dist/client/widgets/translate-document/ui/TranslateDocument.export.d.ts +3 -1
  42. package/dist/client/widgets/translate-document/ui/TranslateDocument.export.js +5 -2
  43. package/dist/client/widgets/translate-document/ui/TranslateDocument.js +7 -3
  44. package/dist/client/widgets/translate-document/ui/TranslateDocument.server.d.ts +2 -0
  45. package/dist/client/widgets/translate-document/ui/TranslateDocument.server.js +2 -1
  46. package/dist/composition/levels/collectionLevel.js +1 -1
  47. package/dist/composition/levels/documentLevel.js +1 -1
  48. package/dist/index.d.ts +1 -0
  49. package/dist/plugin.d.ts +10 -0
  50. package/dist/plugin.js +3 -2
  51. package/dist/server/features/enqueue-translation/handler.js +28 -8
  52. package/dist/server/features/enqueue-translation/model.d.ts +3 -3
  53. package/dist/server/features/enqueue-translation/model.js +6 -1
  54. package/dist/server/features/enqueue-translation/resolveTargetLocales.d.ts +29 -0
  55. package/dist/server/features/enqueue-translation/resolveTargetLocales.js +41 -0
  56. package/dist/server/modules/auto-translate/index.d.ts +2 -0
  57. package/dist/server/modules/auto-translate/index.js +2 -0
  58. package/dist/server/modules/translation-levels/PluginConfigBuilder.d.ts +2 -0
  59. package/dist/server/modules/translation-levels/PluginConfigBuilder.js +2 -0
  60. package/dist/server/modules/translation-levels/types.d.ts +4 -0
  61. package/dist/types/TargetSelection.d.ts +10 -0
  62. package/dist/types/TargetSelection.js +11 -0
  63. package/package.json +1 -1
  64. package/dist/client/entities/translation/api/mutations/useQueueDocumentTranslations.d.ts +0 -8
  65. package/dist/client/entities/translation/api/mutations/useQueueDocumentTranslations.js +0 -20
  66. package/dist/client/shared/ui/form/FormSelectLocale/FormSelectLocale.d.ts +0 -11
  67. package/dist/client/shared/ui/form/FormSelectLocale/FormSelectLocale.js +0 -19
  68. package/dist/client/shared/ui/form/FormSelectLocale/index.d.ts +0 -1
  69. package/dist/client/shared/ui/form/FormSelectLocale/index.js +0 -3
@@ -0,0 +1,129 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import classNames from "classnames";
3
+ import { forwardRef, useState } from "react";
4
+ import { Checkbox } from "../Checkbox";
5
+ import Popup from "../Popup";
6
+ import { filterOptions, isAllSelected, selectAllValue, summarizeSelection, toggleValue } from "./multiSelectModel";
7
+ import styles from "./styles.module.scss";
8
+ // Same size ramp as the single Select, driven through CSS custom properties so both controls collapse
9
+ // to an identical footprint.
10
+ const sizes = {
11
+ sm: {
12
+ height: "24px",
13
+ paddingInline: "0.5rem",
14
+ fontSize: "0.875rem"
15
+ },
16
+ md: {
17
+ height: "32px",
18
+ paddingInline: "0.5rem",
19
+ fontSize: "1rem"
20
+ },
21
+ lg: {
22
+ height: "40px",
23
+ paddingInline: "0.75rem",
24
+ fontSize: "1rem"
25
+ }
26
+ };
27
+ /**
28
+ * A controlled multi-select whose collapsed trigger is a `<button>` styled identically to the single
29
+ * `Select` (same border, chevron, focus ring, error + disabled states, size ramp). The multiplicity
30
+ * lives in a checkbox popover with a "select all" row and a progressive filter — the trigger shows a
31
+ * compact summary (`de, fr +2` / `All (N)`) and never grows with the selection. All decision logic
32
+ * lives in `./multiSelectModel`; the popover width tracks the trigger and grows to its content.
33
+ */ const MultiSelect = /*#__PURE__*/ forwardRef(function MultiSelect({ value, onChange, onBlur, options, placeholder = "-", hasError = false, $size = "lg", $fullWidth = true, selectAllLabel = "Select all", filterThreshold = 8, className = "", disabled, style, "aria-invalid": ariaInvalid, ...rest }, ref) {
34
+ const [open, setOpen] = useState(false);
35
+ const [query, setQuery] = useState("");
36
+ // No options to choose → nothing the popover can do; present as disabled (mirrors an empty select).
37
+ const isDisabled = Boolean(disabled) || options.length === 0;
38
+ const selected = new Set(value);
39
+ const allSelected = isAllSelected(value, options);
40
+ const showFilter = options.length > filterThreshold;
41
+ const visibleOptions = showFilter ? filterOptions(options, query) : options;
42
+ const _style = {
43
+ "--height": sizes[$size].height,
44
+ "--padding-inline": sizes[$size].paddingInline,
45
+ "--font-size": sizes[$size].fontSize,
46
+ ...style
47
+ };
48
+ const triggerClassName = classNames(styles.trigger, styles[`size-${$size}`], {
49
+ [styles.triggerError]: hasError,
50
+ [styles.auto]: !$fullWidth
51
+ }, className);
52
+ const trigger = /*#__PURE__*/ _jsx("button", {
53
+ ref: ref,
54
+ type: "button",
55
+ className: triggerClassName,
56
+ style: _style,
57
+ disabled: isDisabled,
58
+ "aria-invalid": ariaInvalid ?? hasError,
59
+ ...rest,
60
+ children: /*#__PURE__*/ _jsx("span", {
61
+ className: styles.summary,
62
+ children: summarizeSelection(value, options, placeholder)
63
+ })
64
+ });
65
+ const handleOpenChange = (next)=>{
66
+ setOpen(next);
67
+ // Popover closed → the user is done with the field; only now mark it touched.
68
+ if (!next) onBlur?.();
69
+ };
70
+ return /*#__PURE__*/ _jsx(Popup, {
71
+ open: open && !isDisabled,
72
+ onOpenChange: handleOpenChange,
73
+ $trigger: trigger,
74
+ $align: "start",
75
+ children: /*#__PURE__*/ _jsxs("div", {
76
+ className: styles.menu,
77
+ role: "group",
78
+ "aria-label": selectAllLabel,
79
+ children: [
80
+ showFilter && /*#__PURE__*/ _jsx("input", {
81
+ className: styles.filter,
82
+ type: "text",
83
+ value: query,
84
+ placeholder: "Filter…",
85
+ onChange: (event)=>setQuery(event.target.value),
86
+ "aria-label": "Filter options"
87
+ }),
88
+ /*#__PURE__*/ _jsxs("label", {
89
+ className: styles.option,
90
+ children: [
91
+ /*#__PURE__*/ _jsx(Checkbox, {
92
+ checked: allSelected,
93
+ onChange: ()=>onChange(selectAllValue(value, options)),
94
+ "aria-label": selectAllLabel
95
+ }),
96
+ /*#__PURE__*/ _jsx("span", {
97
+ className: styles.optionLabel,
98
+ children: selectAllLabel
99
+ })
100
+ ]
101
+ }),
102
+ /*#__PURE__*/ _jsx("div", {
103
+ className: styles.divider,
104
+ role: "separator"
105
+ }),
106
+ visibleOptions.map((option)=>/*#__PURE__*/ _jsxs("label", {
107
+ className: styles.option,
108
+ children: [
109
+ /*#__PURE__*/ _jsx(Checkbox, {
110
+ checked: selected.has(option.value),
111
+ onChange: ()=>onChange(toggleValue(value, option.value))
112
+ }),
113
+ /*#__PURE__*/ _jsx("span", {
114
+ className: styles.optionCode,
115
+ children: option.value
116
+ }),
117
+ option.label !== option.value && /*#__PURE__*/ _jsx("span", {
118
+ className: styles.optionLabel,
119
+ children: option.label
120
+ })
121
+ ]
122
+ }, option.value))
123
+ ]
124
+ })
125
+ });
126
+ });
127
+ export default MultiSelect;
128
+
129
+ //# sourceMappingURL=MultiSelect.js.map
@@ -0,0 +1,2 @@
1
+ export { default } from "./MultiSelect";
2
+ export type { MultiSelectOption } from "./multiSelectModel";
@@ -0,0 +1,3 @@
1
+ export { default } from "./MultiSelect";
2
+
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,25 @@
1
+ /** An option in a {@link MultiSelect} — same shape as the single `Select`'s option list. */
2
+ export type MultiSelectOption = {
3
+ value: string;
4
+ label: string;
5
+ };
6
+ /**
7
+ * The selected values in option order, restricted to values that still exist in `options`. Using this
8
+ * everywhere (display + counts) keeps the control stable when the selection holds a stale value that is
9
+ * no longer a configured option — it is simply ignored rather than skewing the summary.
10
+ */
11
+ export declare function orderedSelection(value: string[], options: MultiSelectOption[]): string[];
12
+ /** True when every option is selected (and there is at least one option). */
13
+ export declare function isAllSelected(value: string[], options: MultiSelectOption[]): boolean;
14
+ /** Toggle one value in/out of the selection, preserving the existing order (append on add). */
15
+ export declare function toggleValue(value: string[], option: string): string[];
16
+ /** The next value for a "select all" toggle: clear when all are already selected, else select all. */
17
+ export declare function selectAllValue(value: string[], options: MultiSelectOption[]): string[];
18
+ /**
19
+ * Compact single-line trigger summary — `placeholder` when nothing is selected, `All (N)` when every
20
+ * option is, otherwise up to two locale codes followed by a `+N` overflow (e.g. `de, fr +2`). Ordered
21
+ * by option order so it never depends on the click order.
22
+ */
23
+ export declare function summarizeSelection(value: string[], options: MultiSelectOption[], placeholder: string): string;
24
+ /** Case-insensitive filter over option value + label; a blank/whitespace query returns all options. */
25
+ export declare function filterOptions(options: MultiSelectOption[], query: string): MultiSelectOption[];
@@ -0,0 +1,41 @@
1
+ /** An option in a {@link MultiSelect} — same shape as the single `Select`'s option list. */ /**
2
+ * The selected values in option order, restricted to values that still exist in `options`. Using this
3
+ * everywhere (display + counts) keeps the control stable when the selection holds a stale value that is
4
+ * no longer a configured option — it is simply ignored rather than skewing the summary.
5
+ */ export function orderedSelection(value, options) {
6
+ const selected = new Set(value);
7
+ return options.filter((option)=>selected.has(option.value)).map((option)=>option.value);
8
+ }
9
+ /** True when every option is selected (and there is at least one option). */ export function isAllSelected(value, options) {
10
+ if (options.length === 0) return false;
11
+ const selected = new Set(value);
12
+ return options.every((option)=>selected.has(option.value));
13
+ }
14
+ /** Toggle one value in/out of the selection, preserving the existing order (append on add). */ export function toggleValue(value, option) {
15
+ return value.includes(option) ? value.filter((v)=>v !== option) : [
16
+ ...value,
17
+ option
18
+ ];
19
+ }
20
+ /** The next value for a "select all" toggle: clear when all are already selected, else select all. */ export function selectAllValue(value, options) {
21
+ return isAllSelected(value, options) ? [] : options.map((option)=>option.value);
22
+ }
23
+ /**
24
+ * Compact single-line trigger summary — `placeholder` when nothing is selected, `All (N)` when every
25
+ * option is, otherwise up to two locale codes followed by a `+N` overflow (e.g. `de, fr +2`). Ordered
26
+ * by option order so it never depends on the click order.
27
+ */ export function summarizeSelection(value, options, placeholder) {
28
+ const selected = orderedSelection(value, options);
29
+ if (selected.length === 0) return placeholder;
30
+ if (isAllSelected(value, options)) return `All (${options.length})`;
31
+ const shown = selected.slice(0, 2).join(", ");
32
+ const extra = selected.length - 2;
33
+ return extra > 0 ? `${shown} +${extra}` : shown;
34
+ }
35
+ /** Case-insensitive filter over option value + label; a blank/whitespace query returns all options. */ export function filterOptions(options, query) {
36
+ const q = query.trim().toLowerCase();
37
+ if (q.length === 0) return options;
38
+ return options.filter((option)=>option.value.toLowerCase().includes(q) || option.label.toLowerCase().includes(q));
39
+ }
40
+
41
+ //# sourceMappingURL=multiSelectModel.js.map
@@ -0,0 +1,126 @@
1
+ // The collapsed trigger is a <button> made to look identical to the single Select's <select>:
2
+ // same box, same inline-SVG chevron, same focus ring / error / disabled states and size ramp.
3
+ .trigger {
4
+ display: flex;
5
+ align-items: center;
6
+ width: 100%;
7
+ height: var(--height, 40px);
8
+ padding-top: calc((var(--height, 40px) - var(--font-size, 1rem) * 1.2) / 2);
9
+ padding-bottom: calc((var(--height, 40px) - var(--font-size, 1rem) * 1.2) / 2);
10
+ padding-left: var(--padding-inline, 0.75rem);
11
+ padding-right: calc(var(--padding-inline, 0.75rem) + 24px); // room for the chevron
12
+ border: 1px solid var(--theme-elevation-150);
13
+ border-radius: var(--style-radius-s);
14
+ background-color: var(--theme-input-bg);
15
+ color: var(--theme-elevation-800);
16
+ font-size: var(--font-size, 1rem);
17
+ text-align: left;
18
+ cursor: pointer;
19
+ flex-shrink: 0;
20
+
21
+ // Same chevron as Select (inline SVG in the background), right-aligned.
22
+ appearance: none;
23
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 20 20' %3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
24
+ background-position: right var(--padding-inline, 0.75rem) center;
25
+ background-repeat: no-repeat;
26
+ background-size: 16px 16px;
27
+
28
+ &:focus {
29
+ outline: 2px solid var(--theme-success-200);
30
+ outline-offset: -1px;
31
+ border-color: var(--theme-success-500);
32
+ }
33
+
34
+ &:disabled {
35
+ opacity: 0.5;
36
+ cursor: not-allowed;
37
+ }
38
+ }
39
+
40
+ // Opt out of full width (`$fullWidth={false}`) — size to content, e.g. inline in a row.
41
+ .auto {
42
+ width: auto;
43
+ }
44
+
45
+ .size-sm {
46
+ background-size: 12px 12px;
47
+ }
48
+
49
+ .size-md {
50
+ background-size: 14px 14px;
51
+ }
52
+
53
+ .size-lg {
54
+ background-size: 16px 16px;
55
+ }
56
+
57
+ .triggerError {
58
+ border-color: var(--theme-error-600);
59
+ background-color: var(--theme-error-100);
60
+
61
+ &:focus {
62
+ outline: 2px solid var(--theme-error-200);
63
+ outline-offset: -1px;
64
+ }
65
+ }
66
+
67
+ .summary {
68
+ flex: 1;
69
+ min-width: 0;
70
+ overflow: hidden;
71
+ text-overflow: ellipsis;
72
+ white-space: nowrap;
73
+ }
74
+
75
+ .menu {
76
+ display: flex;
77
+ flex-direction: column;
78
+ gap: 0.125rem;
79
+ // Never narrower than the trigger, but grow to fit the option content (Radix exposes the width).
80
+ min-width: var(--radix-popover-trigger-width);
81
+ width: max-content;
82
+ max-width: 320px;
83
+ max-height: 280px;
84
+ overflow-y: auto;
85
+ }
86
+
87
+ .filter {
88
+ margin-bottom: 0.25rem;
89
+ padding: 0.25rem 0.5rem;
90
+ border: 1px solid var(--theme-elevation-150);
91
+ border-radius: var(--style-radius-s);
92
+ background: var(--theme-input-bg);
93
+ color: var(--theme-text);
94
+ font-size: 0.875rem;
95
+ }
96
+
97
+ .option {
98
+ display: flex;
99
+ align-items: center;
100
+ gap: 0.5rem;
101
+ padding: 0.25rem 0.375rem;
102
+ border-radius: var(--style-radius-s);
103
+ cursor: pointer;
104
+ font-size: 0.875rem;
105
+ white-space: nowrap;
106
+
107
+ &:hover {
108
+ background: var(--theme-elevation-100);
109
+ }
110
+ }
111
+
112
+ .optionCode {
113
+ font-variant-numeric: tabular-nums;
114
+ text-transform: uppercase;
115
+ font-size: 0.8125rem;
116
+ }
117
+
118
+ .optionLabel {
119
+ color: var(--theme-elevation-600);
120
+ }
121
+
122
+ .divider {
123
+ height: 1px;
124
+ margin: 0.125rem 0;
125
+ background: var(--theme-elevation-150);
126
+ }
@@ -1,7 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { FormCheckbox } from "../FormCheckbox";
3
- import Tooltip from "../../Tooltip";
4
- import { QuestionCircleIcon } from "../../../lib/assets/icons/QuestionCircleIcon";
3
+ import InfoPopover from "../../InfoPopover";
5
4
  import styles from "./styles.module.scss";
6
5
  const PUBLISH_LABEL = "Publish after translation";
7
6
  const PUBLISH_TOOLTIP = /*#__PURE__*/ _jsxs(_Fragment, {
@@ -15,13 +14,11 @@ const PublishLabel = /*#__PURE__*/ _jsxs("span", {
15
14
  className: styles.label,
16
15
  children: [
17
16
  PUBLISH_LABEL,
18
- /*#__PURE__*/ _jsx(Tooltip, {
17
+ /*#__PURE__*/ _jsx(InfoPopover, {
18
+ label: "What does publish after translation do?",
19
19
  content: PUBLISH_TOOLTIP,
20
20
  side: "bottom",
21
- children: /*#__PURE__*/ _jsx("span", {
22
- className: styles.label__icon,
23
- children: /*#__PURE__*/ _jsx(QuestionCircleIcon, {})
24
- })
21
+ className: styles.label__icon
25
22
  })
26
23
  ]
27
24
  });
@@ -0,0 +1,22 @@
1
+ import type { ReactElement } from "react";
2
+ import type { MultiSelectOption } from "../../MultiSelect";
3
+ type FormMultiSelectProps = {
4
+ name: string;
5
+ required?: boolean;
6
+ options: MultiSelectOption[];
7
+ label?: string | ReactElement;
8
+ description?: string;
9
+ placeholder?: string;
10
+ disabled?: boolean;
11
+ size?: "sm" | "md" | "lg";
12
+ "aria-label"?: string;
13
+ "aria-labelledby"?: string;
14
+ className?: string;
15
+ };
16
+ /**
17
+ * Binds a react-hook-form field (a `string[]` value) to the {@link MultiSelect} primitive, with the
18
+ * same label / description / error affordances as {@link FormSelect}. The single-select analogue is
19
+ * `FormSelect`; this is its multi counterpart. A non-array field value is coerced to `[]`.
20
+ */
21
+ export default function FormMultiSelect({ name, required, options, label, description, placeholder, disabled, size, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className, }: FormMultiSelectProps): import("react/jsx-runtime").JSX.Element;
22
+ export {};
@@ -0,0 +1,70 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useId } from "react";
3
+ import { useController } from "react-hook-form";
4
+ import Description from "../../Description";
5
+ import Label from "../../Label";
6
+ import MultiSelect from "../../MultiSelect";
7
+ /**
8
+ * Binds a react-hook-form field (a `string[]` value) to the {@link MultiSelect} primitive, with the
9
+ * same label / description / error affordances as {@link FormSelect}. The single-select analogue is
10
+ * `FormSelect`; this is its multi counterpart. A non-array field value is coerced to `[]`.
11
+ */ export default function FormMultiSelect({ name, required, options, label, description, placeholder, disabled, size, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, className }) {
12
+ const { field, fieldState, formState } = useController({
13
+ name,
14
+ rules: {
15
+ required
16
+ }
17
+ });
18
+ const errorId = useId();
19
+ const descriptionId = useId();
20
+ const value = Array.isArray(field.value) ? field.value : [];
21
+ const isDisabled = field.disabled || formState.disabled || formState.isSubmitting || disabled;
22
+ const control = /*#__PURE__*/ _jsx(MultiSelect, {
23
+ ref: field.ref,
24
+ value: value,
25
+ onChange: field.onChange,
26
+ onBlur: field.onBlur,
27
+ options: options,
28
+ name: field.name,
29
+ placeholder: placeholder,
30
+ disabled: isDisabled,
31
+ hasError: fieldState.invalid,
32
+ $size: size,
33
+ "aria-describedby": `${errorId} ${descriptionId}`,
34
+ "aria-label": label ? undefined : ariaLabel ?? "Select options",
35
+ "aria-labelledby": label ? undefined : ariaLabelledby
36
+ });
37
+ const errorAndDescription = /*#__PURE__*/ _jsxs(_Fragment, {
38
+ children: [
39
+ fieldState.error && /*#__PURE__*/ _jsx(Description, {
40
+ variant: "error",
41
+ id: errorId,
42
+ children: fieldState.error.message
43
+ }),
44
+ description && /*#__PURE__*/ _jsx(Description, {
45
+ id: descriptionId,
46
+ children: description
47
+ })
48
+ ]
49
+ });
50
+ if (!label) {
51
+ return /*#__PURE__*/ _jsxs("div", {
52
+ className: className,
53
+ children: [
54
+ control,
55
+ errorAndDescription
56
+ ]
57
+ });
58
+ }
59
+ return /*#__PURE__*/ _jsxs(Label, {
60
+ className: className,
61
+ label: label,
62
+ $size: size,
63
+ children: [
64
+ control,
65
+ errorAndDescription
66
+ ]
67
+ });
68
+ }
69
+
70
+ //# sourceMappingURL=FormMultiSelect.js.map
@@ -0,0 +1 @@
1
+ export { default } from "./FormMultiSelect";
@@ -0,0 +1,3 @@
1
+ export { default } from "./FormMultiSelect";
2
+
3
+ //# sourceMappingURL=index.js.map
@@ -1,7 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import FormSelect from "../FormSelect";
3
- import Tooltip from "../../Tooltip";
4
- import { QuestionCircleIcon } from "../../../lib/assets/icons/QuestionCircleIcon";
3
+ import InfoPopover from "../../InfoPopover";
5
4
  import styles from "./styles.module.scss";
6
5
  const STRATEGY_OPTIONS = [
7
6
  {
@@ -31,13 +30,11 @@ const StrategyLabel = /*#__PURE__*/ _jsxs("span", {
31
30
  className: styles.label,
32
31
  children: [
33
32
  STRATEGY_LABEL,
34
- /*#__PURE__*/ _jsx(Tooltip, {
33
+ /*#__PURE__*/ _jsx(InfoPopover, {
34
+ label: "What is update mode?",
35
35
  content: STRATEGY_TOOLTIP,
36
36
  side: "bottom",
37
- children: /*#__PURE__*/ _jsx("span", {
38
- className: styles.label__icon,
39
- children: /*#__PURE__*/ _jsx(QuestionCircleIcon, {})
40
- })
37
+ className: styles.label__icon
41
38
  })
42
39
  ]
43
40
  });
@@ -1,7 +1,9 @@
1
1
  import type { AutoTranslateSummary } from "../../../entities/translation/model/autoTranslateSummary";
2
+ import type { TargetSelectionMode } from "../../../../types/TargetSelection";
2
3
  type BulkTranslationDashboardProps = {
3
4
  hasDrafts: boolean;
4
5
  autoTranslate: AutoTranslateSummary | null;
6
+ targetSelection: TargetSelectionMode;
5
7
  };
6
- export default function BulkTranslationDashboard({ hasDrafts, autoTranslate, }: BulkTranslationDashboardProps): import("react/jsx-runtime").JSX.Element;
8
+ export default function BulkTranslationDashboard({ hasDrafts, autoTranslate, targetSelection, }: BulkTranslationDashboardProps): import("react/jsx-runtime").JSX.Element;
7
9
  export {};
@@ -1,8 +1,10 @@
1
1
  import type { AccessGuard } from "../../../../types/AccessGuard";
2
2
  import type { RawPayloadComponentExport } from "../../../../types/PayloadComponentExport";
3
+ import type { TargetSelectionMode } from "../../../../types/TargetSelection";
3
4
  export declare class BulkDocumentTranslationDashboard implements RawPayloadComponentExport {
4
5
  readonly access: AccessGuard;
6
+ readonly targetSelection: TargetSelectionMode;
5
7
  serverProps?: object | Record<string, any> | undefined;
6
- constructor(access?: AccessGuard);
8
+ constructor(access?: AccessGuard, targetSelection?: TargetSelectionMode);
7
9
  path: string;
8
10
  }
@@ -2,12 +2,15 @@ import { AnyAccessGuard } from "../../../../server/shared";
2
2
  import { clientComponentPath } from "../../../shared/utils/componentPath";
3
3
  export class BulkDocumentTranslationDashboard {
4
4
  access;
5
+ targetSelection;
5
6
  serverProps;
6
- constructor(access = new AnyAccessGuard()){
7
+ constructor(access = new AnyAccessGuard(), targetSelection = "single"){
7
8
  this.access = access;
9
+ this.targetSelection = targetSelection;
8
10
  this.path = clientComponentPath("widgets/bulk-translation-dashboard/ui/BulkTranslationDashboard.server");
9
11
  this.serverProps = {
10
- access
12
+ access,
13
+ targetSelection
11
14
  };
12
15
  }
13
16
  path;
@@ -11,7 +11,7 @@ import { CollectionTranslationProgress } from "../../../features/collection-tran
11
11
  import { handleFormError } from "../../../shared/lib/forms/handle-form-error";
12
12
  import { useCollectionDashboardUrlParams } from "../../../shared/lib/payload/hooks/useCollectionDashboardUrlParams";
13
13
  import styles from "./styles.module.scss";
14
- export default function BulkTranslationDashboard({ hasDrafts, autoTranslate }) {
14
+ export default function BulkTranslationDashboard({ hasDrafts, autoTranslate, targetSelection }) {
15
15
  const locale = useLocale();
16
16
  const { collection } = useCollectionDashboardUrlParams();
17
17
  const documentsSelection = useSelection();
@@ -28,10 +28,14 @@ export default function BulkTranslationDashboard({ hasDrafts, autoTranslate }) {
28
28
  ]);
29
29
  const initialValues = useMemo(()=>({
30
30
  [FORM_FIELDS.SOURCE_LNG]: locale.code,
31
+ // Multi mode binds an array; single mode a string. Seed the matching empty value so the field
32
+ // starts with the right shape (and invalid until a target is chosen).
33
+ [FORM_FIELDS.TARGET_LNG]: targetSelection === "multi" ? [] : "",
31
34
  [FORM_FIELDS.HIDDEN_COLLECTION_SLUG]: collection
32
35
  }), [
33
36
  locale.code,
34
- collection
37
+ collection,
38
+ targetSelection
35
39
  ]);
36
40
  const { form } = CollectionTranslationFormModel.useForm({
37
41
  initialValues,
@@ -82,7 +86,8 @@ export default function BulkTranslationDashboard({ hasDrafts, autoTranslate }) {
82
86
  form: form,
83
87
  onSubmit: handleSubmit,
84
88
  selectedCount: selectedCount,
85
- hasDrafts: hasDrafts
89
+ hasDrafts: hasDrafts,
90
+ targetSelection: targetSelection
86
91
  })
87
92
  ]
88
93
  }),
@@ -1,7 +1,9 @@
1
1
  import type { BeforeListTableServerProps } from "payload";
2
2
  import type { AccessGuard } from "../../../../types/AccessGuard";
3
+ import type { TargetSelectionMode } from "../../../../types/TargetSelection";
3
4
  type BulkTranslationDashboardServerProps = BeforeListTableServerProps & {
4
5
  access: AccessGuard;
6
+ targetSelection: TargetSelectionMode;
5
7
  };
6
8
  declare const BulkTranslationDashboardServer: (props: BulkTranslationDashboardServerProps) => Promise<import("react/jsx-runtime").JSX.Element | null>;
7
9
  export default BulkTranslationDashboardServer;
@@ -19,7 +19,8 @@ const BulkTranslationDashboardServer = async (props)=>{
19
19
  const autoTranslate = resolveAutoTranslateSummary(collection, props.payload.config.localization ? props.payload.config.localization.defaultLocale : undefined);
20
20
  return /*#__PURE__*/ _jsx(BulkTranslationDashboard, {
21
21
  hasDrafts: hasDrafts,
22
- autoTranslate: autoTranslate
22
+ autoTranslate: autoTranslate,
23
+ targetSelection: props.targetSelection
23
24
  });
24
25
  };
25
26
  export default BulkTranslationDashboardServer;
@@ -1,7 +1,9 @@
1
1
  import type { AutoTranslateSummary } from "../../../entities/translation/model/autoTranslateSummary";
2
+ import type { TargetSelectionMode } from "../../../../types/TargetSelection";
2
3
  type TranslateDocumentProps = {
3
4
  hasDrafts: boolean;
4
5
  autoTranslate: AutoTranslateSummary | null;
6
+ targetSelection: TargetSelectionMode;
5
7
  };
6
- declare const TranslateDocument: ({ hasDrafts, autoTranslate }: TranslateDocumentProps) => import("react/jsx-runtime").JSX.Element;
8
+ declare const TranslateDocument: ({ hasDrafts, autoTranslate, targetSelection, }: TranslateDocumentProps) => import("react/jsx-runtime").JSX.Element;
7
9
  export default TranslateDocument;
@@ -1,10 +1,12 @@
1
1
  import type { CollectionConfig } from "payload";
2
2
  import type { AccessGuard } from "../../../../types/AccessGuard";
3
3
  import type { RawPayloadComponentExport } from "../../../../types/PayloadComponentExport";
4
+ import type { TargetSelectionMode } from "../../../../types/TargetSelection";
4
5
  export declare class TranslateDocumentExport implements RawPayloadComponentExport {
5
6
  readonly collection: CollectionConfig;
6
7
  readonly access: AccessGuard;
8
+ readonly targetSelection: TargetSelectionMode;
7
9
  serverProps?: object | Record<string, any> | undefined;
8
- constructor(collection: CollectionConfig, access?: AccessGuard);
10
+ constructor(collection: CollectionConfig, access?: AccessGuard, targetSelection?: TargetSelectionMode);
9
11
  path: string;
10
12
  }
@@ -3,14 +3,17 @@ import { clientComponentPath } from "../../../shared/utils/componentPath";
3
3
  export class TranslateDocumentExport {
4
4
  collection;
5
5
  access;
6
+ targetSelection;
6
7
  serverProps;
7
- constructor(collection, access = new AnyAccessGuard()){
8
+ constructor(collection, access = new AnyAccessGuard(), targetSelection = "single"){
8
9
  this.collection = collection;
9
10
  this.access = access;
11
+ this.targetSelection = targetSelection;
10
12
  this.path = clientComponentPath("widgets/translate-document/ui/TranslateDocument.server");
11
13
  this.serverProps = {
12
14
  collection,
13
- access
15
+ access,
16
+ targetSelection
14
17
  };
15
18
  }
16
19
  path;