@formatjs/editor 1.4.0 → 1.5.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 (5) hide show
  1. package/README.md +160 -5
  2. package/package.json +1 -1
  3. package/ui.d.ts +193 -63
  4. package/ui.js +371 -59
  5. package/ui.js.map +1 -1
package/README.md CHANGED
@@ -194,8 +194,7 @@ It is separate from the headless entry point; consumers can use any design syste
194
194
  Import `TranslationEditorView`, `MessageList`, `SourceMessage`, and
195
195
  `TranslationField` from `@formatjs/editor/ui`. This separate entry point owns
196
196
  message-row selection wiring, field labels and error descriptions, draft status,
197
- and copy/reset/save controls. It ships unstyled native controls and requires only
198
- React. The headless root does not import the UI, StyleX, icons, or React Intl.
197
+ and copy/reset/save controls. It ships native controls and uses React plus the package's existing ICU parser. The headless root does not import the UI, StyleX, icons, or React Intl.
199
198
 
200
199
  Mount one workflow above the views and pass its existing drafts:
201
200
 
@@ -243,7 +242,8 @@ navigation busy and displays a status; it does not clear the controlled list.
243
242
  ### Component adapters
244
243
 
245
244
  `EditorDesignSystemProvider` accepts partial overrides of `EditorComponents`: `Button`,
246
- `TextInput`, `TextArea`, `MessageRow`, `Panel`, and `Layout`. Unspecified entries
245
+ `TextInput`, `TextArea`, `MessageRow`, `Panel`, and `Layout`, plus the optional tool
246
+ adapters described below. Unspecified or explicitly `undefined` entries
247
247
  inherit from the nearest provider, falling back to `nativeEditorComponents`. Adapters map a design system's control API to
248
248
  semantic `onPress`, `onSelect`, and `onValueChange` callbacks. Define adapters at
249
249
  module scope so React preserves focus and control state between edits:
@@ -290,7 +290,7 @@ function CustomToolbar() {
290
290
 
291
291
  All built-in views and downstream consumers use `useEditorDesignSystem()`;
292
292
  there is no component-registry prop on individual views. The hook returns the
293
- resolved, read-only `EditorComponents` contract. Providers are React-tree scoped,
293
+ resolved, read-only `ResolvedEditorComponents` contract, including native defaults for every optional tool adapter. Providers are React-tree scoped,
294
294
  so sibling editors (and separate server-rendered trees) do not share mutable
295
295
  configuration. Nested providers override only specified components and inherit
296
296
  the rest. Changing the registry updates consumers; keeping each component type
@@ -306,7 +306,7 @@ Each component has an exported props contract:
306
306
  | `TextArea` / `EditorTextAreaProps` | `id`, `value`, optional `rows` (default six), disabled/error-description attributes | `onValueChange(value: string): void` |
307
307
  | `MessageRow` / `EditorMessageRowProps` | `children`, controlled `selected` | `onSelect(): void` |
308
308
  | `Panel` / `EditorPanelProps` | `children`, accessible `label`, `kind` (`source` or `translation`) | None; layout only |
309
- | `Layout` / `EditorLayoutProps` | `toolbar`, `navigation`, `content` nodes | None; layout only |
309
+ | `Layout` / `EditorLayoutProps` | `toolbar`, `navigation`, `content`, optional `sidebar` nodes | None; layout only |
310
310
 
311
311
  `EditorInputProps` defines the shared input attributes explicitly: `id`, `value`,
312
312
  `onValueChange`, optional `disabled`, `aria-invalid`, and `aria-describedby`.
@@ -334,6 +334,161 @@ The reusable pieces retain semantic labels, headings, lists, alerts, and status
334
334
  nodes. Adapters control the interactive controls and outer presentation; use the
335
335
  standalone pieces when a different page composition is needed.
336
336
 
337
+ ### Reusable translation tools
338
+
339
+ `LocalePicker`, `MessagePreview`, `CopyTextButton`, and `MessageContext` are also
340
+ exported from `@formatjs/editor/ui`. They use the same `EditorDesignSystemProvider`
341
+ and work independently or inside the composed view's slots:
342
+
343
+ ```tsx
344
+ import {
345
+ CopyTextButton,
346
+ LocalePicker,
347
+ MessageContext,
348
+ MessagePreview,
349
+ } from '@formatjs/editor/ui'
350
+
351
+ function TranslationTools() {
352
+ return (
353
+ <>
354
+ <LocalePicker
355
+ locales={availableLocales}
356
+ selectedLocales={selectedLocales}
357
+ onChange={setSelectedLocales}
358
+ getLocaleLabel={locale => displayNames.of(locale) ?? locale}
359
+ />
360
+
361
+ <MessagePreview message={draft.value} />
362
+ <CopyTextButton value={draft.value} label="translation" />
363
+ <MessageContext message={selectedMessage} />
364
+ </>
365
+ )
366
+ }
367
+ ```
368
+
369
+ - **LocalePicker** reports unique available locale codes in input order through
370
+ `onChange(locales: string[])`. Selection is controlled; duplicate and unavailable
371
+ selections do not distort select-all or mixed state. A selection change drops
372
+ unavailable codes. The picker uses unique label/control IDs and balanced,
373
+ contiguous columns that collapse without changing reading order. Empty lists
374
+ disable select-all. `getLocaleLabel` defaults to the code so display language
375
+ stays explicit. `labels` overrides `title`, `empty`, `clear`, and the formatter
376
+ callbacks `selectAll(count)`, `selected(count)`, and `trigger(summary)`.
377
+ - **MessagePreview** shows ICU structure, not a formatted sample result: all plural
378
+ and select branches, ordinal types, plural offsets, rich-text tags, and exact
379
+ number/date/time format tokens (including skeletons). Literals retain whitespace;
380
+ tags are displayed as text, never executed as HTML. Incomplete syntax produces
381
+ an alert; `formatError(error)` can localize its contents. No argument values are
382
+ required, and empty text is valid.
383
+ - **CopyTextButton** copies `value` verbatim through the browser Clipboard API.
384
+ `writeText(value): Promise<void>` can supply a different clipboard implementation,
385
+ including a host-specific fallback. Unsupported access and rejected writes show
386
+ failure feedback, never success. Pending writes disable the control. Changing
387
+ the value or writer, or unmounting, invalidates old results and callbacks.
388
+ `onCopy(value)` and `onError(error, value)` report completed current requests.
389
+ Feedback expires after `feedbackDurationMs` (default 1500); `labels` provides
390
+ `copy`, `copying`, `copied`, and `failed` formatters receiving the supplied label.
391
+ Native clipboard access happens only when the user presses the button.
392
+ - **MessageContext** renders an ID, optional description, catalogs, and source
393
+ locations from the existing `EditorMessage` metadata contract. Locations render
394
+ as plain text with optional start/end ranges; `renderLocation(location)` can
395
+ customize them. `copyId` defaults to true and `copyOptions` passes clipboard
396
+ callbacks, labels, writer, and timing to the ID's copy control. `labels` overrides
397
+ `title`, `id`, `description`, `catalogs`, and `locations`. A missing message clears
398
+ the content; absent metadata sections are omitted.
399
+
400
+ Tool adapters are optional in `EditorComponents`, so existing complete registries
401
+ remain valid. The hook resolves all of them to native defaults. Override them in
402
+ one provider to use the same design system across fields and standalone tools:
403
+
404
+ | Adapter / props | Inputs | Output callback |
405
+ | ------------------------------------------------------ | --------------------------------------------------------------------------------------- | ----------------------------------- |
406
+ | `Checkbox` / `EditorCheckboxProps` | `id`, `checked` (boolean or `indeterminate`), optional `disabled` | `onCheckedChange(checked: boolean)` |
407
+ | `LocalePickerLayout` / `EditorLocalePickerLayoutProps` | `id`, `title`, `summary`, `triggerLabel`, `open`, `controls`, ordered `columns` | `onOpenChange(open: boolean)` |
408
+ | `PreviewToken` / `EditorPreviewTokenProps` | `children`, `kind` (argument, number, date, time, tag, selector, plural, pound, syntax) | None |
409
+ | `CopyButton` / `EditorCopyButtonProps` | `label`, `status` (idle, copying, copied, error), `disabled` | `onPress()` |
410
+ | `Metadata` / `EditorMetadataProps` | Accessible `label`, `children` | None |
411
+
412
+ Checkbox adapters forward IDs, expose mixed state, honor disabled state, and report
413
+ booleans without DOM events. Locale-picker layouts can use a popover, disclosure,
414
+ or dialog: they own open-state interaction, focus handling, and responsive layout,
415
+ while preserving column order and rendering every control. Copy-button adapters
416
+ can map status to icons, but must retain the accessible label and must not submit
417
+ forms. Preview-token adapters render children as text/content, never raw HTML.
418
+ The tools demo (`demo/tools-demo.tsx`, browser fixture `/?tools=1`) combines these
419
+ components with the StyleX registry.
420
+
421
+ ### Custom message rows and composed layouts
422
+
423
+ `MessageList` and `TranslationEditorView` infer your message type from `messages`,
424
+ so row renderers retain application metadata without casts. `renderMessage` receives
425
+ `(message, {selected})` and returns **noninteractive** content inside the existing
426
+ selection control. `renderMessageActions` receives the same inputs and renders
427
+ sibling controls in the list item; pressing an action does not select the row.
428
+ Actions should have accessible names and use non-submitting buttons. The library
429
+ continues to own row keys, selection callbacks, list semantics, and search wiring.
430
+ Omit a renderer for the standard content; return `null` to suppress that slot.
431
+
432
+ ```tsx
433
+ const messages = [
434
+ {id: 'greeting', defaultMessage: 'Hello {name}', translatedCount: 3},
435
+ ]
436
+
437
+ function CatalogEditor() {
438
+ const {Button} = useEditorDesignSystem()
439
+ return (
440
+ <TranslationEditorView
441
+ messages={messages}
442
+ selectedId={selectedId}
443
+ selectedMessage={loadedDetail}
444
+ onSelect={selectMessage}
445
+ translations={translations}
446
+ listSummary={<output>{total} results</output>}
447
+ renderMessage={(message, {selected}) => (
448
+ <>
449
+ <span>{message.defaultMessage}</span>
450
+ <span>{message.translatedCount} translations</span>
451
+ {selected && <span>Selected</span>}
452
+ </>
453
+ )}
454
+ renderMessageActions={message => (
455
+ <Button variant="secondary" onPress={() => openReview(message.id)}>
456
+ Review {message.id}
457
+ </Button>
458
+ )}
459
+ renderTranslations={fields => <div className="locale-grid">{fields}</div>}
460
+ renderContent={content => (
461
+ <section aria-label="Translation details">
462
+ {content}
463
+ <p>Drafts remain available when you hide a locale.</p>
464
+ </section>
465
+ )}
466
+ sidebar={<SourceMetadata message={loadedDetail} />}
467
+ emptyState={<output>Loading message details…</output>}
468
+ />
469
+ )
470
+ }
471
+ ```
472
+
473
+ `selectedId` controls list highlighting independently of fetched detail; it defaults
474
+ to `selectedMessage?.id`. `selectedMessage` can remain outside the loaded page.
475
+ `listSummary` appears after search and before the rows/loading status. Lists remain
476
+ caller-controlled: passing `loading` does not clear existing rows.
477
+
478
+ `renderTranslations` wraps the generated locale fields (including an empty array)
479
+ when a message is selected. `renderContent` wraps the whole detail region, including
480
+ notices and the empty state. These are render functions, not component types;
481
+ keep any component types they return stable so fields retain focus. The wrappers
482
+ must render their provided children to retain the built-in editing UI. Draft
483
+ lifetime still belongs to the caller's workflow.
484
+
485
+ `sidebar` is passed separately to the context's `Layout` adapter, which chooses
486
+ its placement and responsive behavior. Layout adapters must render this optional
487
+ prop to support sidebars; existing adapters that do not use sidebars remain valid.
488
+ `emptyState` replaces the default no-selection status (`null` suppresses it).
489
+ Per-locale `translations[].labels` override shared labels, with validation labels
490
+ merged individually, so locale-specific save labels do not drop shared errors.
491
+
337
492
  ### Application slots and localization
338
493
 
339
494
  `filters`, `pagination`, `context`, and `notice` accept React nodes. Supply your
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formatjs/editor",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "private": false,
5
5
  "description": "Headless ICU MessageFormat editor for React",
6
6
  "keywords": [
package/ui.d.ts CHANGED
@@ -1,5 +1,159 @@
1
1
  import { ComponentType, ReactNode } from "react";
2
2
  import "@formatjs/icu-messageformat-parser";
3
+ //#region packages/editor/design-system.d.ts
4
+ interface EditorButtonProps {
5
+ children: ReactNode;
6
+ /** Called once per activation, without a DOM event; disabled controls must not call it. */
7
+ onPress: () => void;
8
+ disabled?: boolean;
9
+ variant: "primary" | "secondary";
10
+ }
11
+ interface EditorInputProps {
12
+ id: string;
13
+ value: string;
14
+ /** Reports the complete next string value; never a DOM event. */
15
+ onValueChange: (value: string) => void;
16
+ disabled?: boolean;
17
+ "aria-invalid"?: boolean;
18
+ "aria-describedby"?: string;
19
+ }
20
+ interface EditorTextInputProps extends EditorInputProps {
21
+ type: "text" | "search";
22
+ }
23
+ interface EditorTextAreaProps extends EditorInputProps {
24
+ /** Visible rows when supported by the control; defaults to six. */
25
+ rows?: number;
26
+ }
27
+ interface EditorMessageRowProps {
28
+ children: ReactNode;
29
+ selected: boolean;
30
+ /** Activates this row without changing controlled selection itself. */
31
+ onSelect: () => void;
32
+ }
33
+ interface EditorPanelProps {
34
+ children: ReactNode;
35
+ label: string;
36
+ kind: "source" | "translation";
37
+ }
38
+ interface EditorLayoutProps {
39
+ toolbar: ReactNode;
40
+ navigation: ReactNode;
41
+ content: ReactNode;
42
+ /** Optional secondary content, arranged by the design-system layout. */
43
+ sidebar?: ReactNode;
44
+ }
45
+ /** Define adapters outside render so controls retain focus across edits. */
46
+ interface EditorComponents extends Partial<EditorToolComponents> {
47
+ Button: ComponentType<EditorButtonProps>;
48
+ TextInput: ComponentType<EditorTextInputProps>;
49
+ TextArea: ComponentType<EditorTextAreaProps>;
50
+ MessageRow: ComponentType<EditorMessageRowProps>;
51
+ Panel: ComponentType<EditorPanelProps>;
52
+ Layout: ComponentType<EditorLayoutProps>;
53
+ }
54
+ interface EditorCheckboxProps {
55
+ id: string;
56
+ checked: boolean | "indeterminate";
57
+ disabled?: boolean;
58
+ onCheckedChange: (checked: boolean) => void;
59
+ }
60
+ interface EditorLocalePickerLayoutProps {
61
+ id: string;
62
+ title: string;
63
+ summary: string;
64
+ triggerLabel: string;
65
+ open: boolean;
66
+ onOpenChange: (open: boolean) => void;
67
+ controls: ReactNode;
68
+ /** Contiguous, balanced columns in the caller's locale order. */
69
+ columns: readonly ReactNode[];
70
+ }
71
+ interface EditorPreviewTokenProps {
72
+ children: ReactNode;
73
+ kind: "argument" | "number" | "date" | "time" | "tag" | "selector" | "plural" | "pound" | "syntax";
74
+ }
75
+ type EditorCopyStatus = "idle" | "copying" | "copied" | "error";
76
+ interface EditorCopyButtonProps {
77
+ label: string;
78
+ status: EditorCopyStatus;
79
+ disabled: boolean;
80
+ onPress: () => void;
81
+ }
82
+ interface EditorMetadataProps {
83
+ label: string;
84
+ children: ReactNode;
85
+ }
86
+ /** Optional additions preserve existing complete design-system registries. */
87
+ interface EditorToolComponents {
88
+ Checkbox: ComponentType<EditorCheckboxProps>;
89
+ LocalePickerLayout: ComponentType<EditorLocalePickerLayoutProps>;
90
+ PreviewToken: ComponentType<EditorPreviewTokenProps>;
91
+ CopyButton: ComponentType<EditorCopyButtonProps>;
92
+ Metadata: ComponentType<EditorMetadataProps>;
93
+ }
94
+ type ResolvedEditorComponents = Required<EditorComponents>;
95
+ /** Unstyled native controls; no CSS, icons, or localization provider is required. */
96
+ export declare const nativeEditorComponents: ResolvedEditorComponents;
97
+ interface EditorDesignSystemProviderProps {
98
+ /** Overrides inherit unspecified components from the nearest provider. */
99
+ components: Partial<EditorComponents>;
100
+ children: ReactNode;
101
+ }
102
+ /** Configure a tree once; sibling providers remain independent. */
103
+ export declare function EditorDesignSystemProvider({ components, children }: EditorDesignSystemProviderProps): ReactNode;
104
+ /** Read the resolved controls, including native defaults outside a provider. */
105
+ export declare function useEditorDesignSystem(): Readonly<ResolvedEditorComponents>;
106
+ //#endregion
107
+ //#region packages/editor/locale-picker.d.ts
108
+ interface LocalePickerLabels {
109
+ title: string;
110
+ empty: string;
111
+ clear: string;
112
+ selectAll: (count: number) => string;
113
+ selected: (count: number) => string;
114
+ trigger: (summary: string) => string;
115
+ }
116
+ interface LocalePickerProps {
117
+ locales: readonly string[];
118
+ selectedLocales: readonly string[];
119
+ /** Reports unique available locales in their input order; never mutates selection. */
120
+ onChange: (locales: string[]) => void;
121
+ /** Defaults to the locale code; consumers choose the display language. */
122
+ getLocaleLabel?: (locale: string) => string;
123
+ labels?: Partial<LocalePickerLabels>;
124
+ }
125
+ export declare function LocalePicker({ locales, selectedLocales, onChange, getLocaleLabel, labels }: LocalePickerProps): ReactNode;
126
+ //#endregion
127
+ //#region packages/editor/message-preview.d.ts
128
+ interface MessagePreviewProps {
129
+ message: string;
130
+ /** Customize a parser error without suppressing the alert semantics. */
131
+ formatError?: (error: Error) => ReactNode;
132
+ }
133
+ /** Structural ICU preview: shows every branch, without evaluating values or HTML. */
134
+ export declare function MessagePreview({ message, formatError }: MessagePreviewProps): ReactNode;
135
+ //#endregion
136
+ //#region packages/editor/copy-text-button.d.ts
137
+ interface CopyTextLabels {
138
+ copy: (label: string) => string;
139
+ copying: (label: string) => string;
140
+ copied: (label: string) => string;
141
+ failed: (label: string) => string;
142
+ }
143
+ interface CopyTextButtonProps {
144
+ value: string;
145
+ label: string;
146
+ disabled?: boolean;
147
+ /** Injectable clipboard boundary; rejects when the write fails. */
148
+ writeText?: (value: string) => Promise<void>;
149
+ onCopy?: (value: string) => void;
150
+ onError?: (error: Error, value: string) => void;
151
+ feedbackDurationMs?: number;
152
+ labels?: Partial<CopyTextLabels>;
153
+ }
154
+ /** Copies exact text; feedback belongs to the current value and mounted control. */
155
+ export declare function CopyTextButton({ value, label, disabled, writeText, onCopy, onError, feedbackDurationMs, labels }: CopyTextButtonProps): ReactNode;
156
+ //#endregion
3
157
  //#region packages/editor/validation.d.ts
4
158
  type TranslationValidationError = "empty" | "invalid-source" | "invalid-translation" | "structure";
5
159
  //#endregion
@@ -44,55 +198,25 @@ interface TranslationDraftState<TContext = void, TResult = void> {
44
198
  save: (context?: TContext) => Promise<TranslationSaveResult<TResult>>;
45
199
  }
46
200
  //#endregion
47
- //#region packages/editor/ui.d.ts
48
- export interface EditorButtonProps {
49
- children: ReactNode;
50
- /** Called once per activation, without a DOM event; disabled controls must not call it. */
51
- onPress: () => void;
52
- disabled?: boolean;
53
- variant: "primary" | "secondary";
54
- }
55
- export interface EditorInputProps {
201
+ //#region packages/editor/message-context.d.ts
202
+ interface MessageContextLabels {
203
+ title: string;
56
204
  id: string;
57
- value: string;
58
- /** Reports the complete next string value; never a DOM event. */
59
- onValueChange: (value: string) => void;
60
- disabled?: boolean;
61
- "aria-invalid"?: boolean;
62
- "aria-describedby"?: string;
63
- }
64
- export interface EditorTextInputProps extends EditorInputProps {
65
- type: "text" | "search";
66
- }
67
- export interface EditorTextAreaProps extends EditorInputProps {
68
- /** Visible rows when supported by the control; defaults to six. */
69
- rows?: number;
70
- }
71
- export interface EditorMessageRowProps {
72
- children: ReactNode;
73
- selected: boolean;
74
- /** Activates this row without changing controlled selection itself. */
75
- onSelect: () => void;
76
- }
77
- export interface EditorPanelProps {
78
- children: ReactNode;
79
- label: string;
80
- kind: "source" | "translation";
205
+ description: string;
206
+ catalogs: string;
207
+ locations: string;
81
208
  }
82
- export interface EditorLayoutProps {
83
- toolbar: ReactNode;
84
- navigation: ReactNode;
85
- content: ReactNode;
86
- }
87
- /** Define adapters outside render so controls retain focus across edits. */
88
- export interface EditorComponents {
89
- Button: ComponentType<EditorButtonProps>;
90
- TextInput: ComponentType<EditorTextInputProps>;
91
- TextArea: ComponentType<EditorTextAreaProps>;
92
- MessageRow: ComponentType<EditorMessageRowProps>;
93
- Panel: ComponentType<EditorPanelProps>;
94
- Layout: ComponentType<EditorLayoutProps>;
209
+ interface MessageContextProps {
210
+ message?: Pick<EditorMessage, "id" | "description" | "catalogs" | "locations"> | null;
211
+ labels?: Partial<MessageContextLabels>;
212
+ copyId?: boolean;
213
+ copyOptions?: Pick<CopyTextButtonProps, "writeText" | "onCopy" | "onError" | "labels" | "feedbackDurationMs">;
214
+ /** Default locations are plain text, never file URLs or HTML. */
215
+ renderLocation?: (location: SourceLocation) => ReactNode;
95
216
  }
217
+ export declare function MessageContext({ message, labels, copyId, copyOptions, renderLocation }: MessageContextProps): ReactNode;
218
+ //#endregion
219
+ //#region packages/editor/ui.d.ts
96
220
  export interface EditorLabels {
97
221
  search: string;
98
222
  messages: string;
@@ -112,17 +236,6 @@ export interface EditorLabels {
112
236
  export type EditorLabelOverrides = Partial<Omit<EditorLabels, "validation">> & {
113
237
  validation?: Partial<EditorLabels["validation"]>;
114
238
  };
115
- /** Unstyled native controls; no CSS, icons, or localization provider is required. */
116
- export declare const nativeEditorComponents: EditorComponents;
117
- export interface EditorDesignSystemProviderProps {
118
- /** Overrides inherit unspecified components from the nearest provider. */
119
- components: Partial<EditorComponents>;
120
- children: ReactNode;
121
- }
122
- /** Configure a tree once; sibling providers remain independent. */
123
- export declare function EditorDesignSystemProvider({ components, children }: EditorDesignSystemProviderProps): ReactNode;
124
- /** Read the resolved controls, including native defaults outside a provider. */
125
- export declare function useEditorDesignSystem(): Readonly<EditorComponents>;
126
239
  interface ViewOptions {
127
240
  labels?: EditorLabelOverrides;
128
241
  }
@@ -131,16 +244,25 @@ export interface EditorSearch {
131
244
  value: string;
132
245
  onValueChange: (value: string) => void;
133
246
  }
134
- export interface MessageListProps extends ViewOptions {
135
- messages: readonly EditorViewMessage[];
247
+ export interface EditorMessageRenderState {
248
+ selected: boolean;
249
+ }
250
+ export interface MessageListProps<Message extends EditorViewMessage = EditorViewMessage> extends ViewOptions {
251
+ messages: readonly Message[];
136
252
  selectedId?: string;
137
253
  onSelect: (id: string) => void;
138
254
  search?: EditorSearch;
139
255
  loading?: boolean;
140
256
  pagination?: ReactNode;
257
+ /** Summary or controls between search and the loaded rows. */
258
+ listSummary?: ReactNode;
259
+ /** Noninteractive content inside the design-system selection control. */
260
+ renderMessage?: (message: Message, state: EditorMessageRenderState) => ReactNode;
261
+ /** Interactive actions rendered beside, never inside, the selection control. */
262
+ renderMessageActions?: (message: Message, state: EditorMessageRenderState) => ReactNode;
141
263
  }
142
264
  /** Controlled list: it never filters, paginates, fetches, or changes selection itself. */
143
- export declare function MessageList({ messages, selectedId, onSelect, search, loading, pagination, labels }: MessageListProps): ReactNode;
265
+ export declare function MessageList<Message extends EditorViewMessage = EditorViewMessage>({ messages, selectedId, onSelect, search, loading, pagination, listSummary, renderMessage, renderMessageActions, labels }: MessageListProps<Message>): ReactNode;
144
266
  export interface SourceMessageProps extends ViewOptions {
145
267
  message: EditorViewMessage;
146
268
  preview?: ReactNode;
@@ -161,8 +283,8 @@ export interface TranslationFieldProps extends ViewOptions {
161
283
  preview?: ReactNode;
162
284
  }
163
285
  export declare function TranslationField({ locale, label, source, draft, onSave, actions, preview, labels }: TranslationFieldProps): ReactNode;
164
- export type EditorTranslation = Omit<TranslationFieldProps, "source" | "labels">;
165
- export interface TranslationEditorViewProps extends Omit<MessageListProps, "selectedId"> {
286
+ export type EditorTranslation = Omit<TranslationFieldProps, "source">;
287
+ export interface TranslationEditorViewProps<Message extends EditorViewMessage = EditorViewMessage> extends MessageListProps<Message> {
166
288
  /** May be outside the loaded list, e.g. during a server-side page change. */
167
289
  selectedMessage?: EditorViewMessage;
168
290
  translations: readonly EditorTranslation[];
@@ -170,8 +292,16 @@ export interface TranslationEditorViewProps extends Omit<MessageListProps, "sele
170
292
  context?: ReactNode;
171
293
  sourcePreview?: ReactNode;
172
294
  notice?: ReactNode;
295
+ sidebar?: ReactNode;
296
+ /** Replaces the no-selection status; null suppresses it. */
297
+ emptyState?: ReactNode;
298
+ /** Wrap the generated locale fields without recreating their wiring. */
299
+ renderTranslations?: (fields: ReactNode) => ReactNode;
300
+ /** Wrap the entire detail region, including notices and the empty state. */
301
+ renderContent?: (content: ReactNode) => ReactNode;
173
302
  }
174
303
  /** A stateless composition over caller-owned navigation and locale drafts. */
175
- export declare function TranslationEditorView({ selectedMessage, translations, filters, context, sourcePreview, notice, labels, ...list }: TranslationEditorViewProps): ReactNode;
304
+ export declare function TranslationEditorView<Message extends EditorViewMessage = EditorViewMessage>({ selectedMessage, selectedId, translations, filters, context, sourcePreview, notice, sidebar, emptyState, renderTranslations, renderContent, labels, ...list }: TranslationEditorViewProps<Message>): ReactNode;
176
305
  //#endregion
306
+ export type { CopyTextButtonProps, CopyTextLabels, EditorButtonProps, EditorCheckboxProps, EditorComponents, EditorCopyButtonProps, EditorCopyStatus, EditorDesignSystemProviderProps, EditorInputProps, EditorLayoutProps, EditorLocalePickerLayoutProps, EditorMessageRowProps, EditorMetadataProps, EditorPanelProps, EditorPreviewTokenProps, EditorTextAreaProps, EditorTextInputProps, EditorToolComponents, LocalePickerLabels, LocalePickerProps, MessageContextLabels, MessageContextProps, MessagePreviewProps, ResolvedEditorComponents };
177
307
  //# sourceMappingURL=ui.d.ts.map
package/ui.js CHANGED
@@ -1,39 +1,58 @@
1
- import { createContext, createElement, useContext, useId, useMemo } from "react";
1
+ import { createContext, createElement, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- //#region packages/editor/ui.tsx
4
- const defaultLabels = {
5
- search: "Search messages",
6
- messages: "Messages",
7
- source: "Source message",
8
- noMessages: "No matching messages",
9
- noSelection: "Select a message",
10
- loading: "Loading messages…",
11
- copySource: "Copy source",
12
- reset: "Reset",
13
- save: "Save translation",
14
- saving: "Saving…",
15
- saved: "Translation saved.",
16
- unsaved: "Unsaved changes",
17
- unchanged: "No unsaved changes",
18
- validation: {
19
- empty: "Enter a translation before saving.",
20
- "invalid-source": "The source contains invalid ICU syntax.",
21
- "invalid-translation": "The translation contains invalid ICU syntax.",
22
- structure: "Preserve ICU arguments, tags, formatting styles, and selector branches."
23
- }
24
- };
25
- function resolveLabels(labels) {
26
- return {
27
- ...defaultLabels,
28
- ...labels,
29
- validation: {
30
- ...defaultLabels.validation,
31
- ...labels?.validation
32
- }
33
- };
34
- }
3
+ import { TYPE, parse } from "@formatjs/icu-messageformat-parser";
4
+ //#region packages/editor/design-system.tsx
35
5
  /** Unstyled native controls; no CSS, icons, or localization provider is required. */
36
6
  const nativeEditorComponents = {
7
+ Checkbox: function NativeCheckbox({ checked, onCheckedChange, ...props }) {
8
+ const ref = useRef(null);
9
+ useEffect(() => {
10
+ if (ref.current) ref.current.indeterminate = checked === "indeterminate";
11
+ }, [checked]);
12
+ return /* @__PURE__ */ jsx("input", {
13
+ ...props,
14
+ ref,
15
+ type: "checkbox",
16
+ checked: checked === true,
17
+ "aria-checked": checked === "indeterminate" ? "mixed" : checked,
18
+ onChange: (event) => onCheckedChange(event.target.checked)
19
+ });
20
+ },
21
+ LocalePickerLayout: ({ id, title, summary, triggerLabel, open, onOpenChange, controls, columns }) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("button", {
22
+ type: "button",
23
+ "aria-label": triggerLabel,
24
+ "aria-expanded": open,
25
+ "aria-controls": id,
26
+ onClick: () => onOpenChange(!open),
27
+ children: summary
28
+ }), /* @__PURE__ */ jsxs("fieldset", {
29
+ id,
30
+ hidden: !open,
31
+ children: [
32
+ /* @__PURE__ */ jsx("legend", { children: title }),
33
+ controls,
34
+ /* @__PURE__ */ jsx("div", {
35
+ style: {
36
+ display: "grid",
37
+ gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 16rem), 1fr))",
38
+ maxHeight: "60vh",
39
+ overflowY: "auto"
40
+ },
41
+ children: columns.map((column, index) => /* @__PURE__ */ jsx("div", { children: column }, index))
42
+ })
43
+ ]
44
+ })] }),
45
+ PreviewToken: ({ children }) => /* @__PURE__ */ jsx("code", { children }),
46
+ CopyButton: ({ label, onPress, disabled }) => /* @__PURE__ */ jsx("button", {
47
+ type: "button",
48
+ disabled,
49
+ onClick: () => onPress(),
50
+ children: label
51
+ }),
52
+ Metadata: ({ label, children }) => /* @__PURE__ */ jsx("aside", {
53
+ "aria-label": label,
54
+ children
55
+ }),
37
56
  Button: ({ children, onPress, disabled }) => /* @__PURE__ */ jsx("button", {
38
57
  type: "button",
39
58
  disabled,
@@ -59,7 +78,11 @@ const nativeEditorComponents = {
59
78
  "aria-label": label,
60
79
  children
61
80
  }),
62
- Layout: ({ toolbar, navigation, content }) => /* @__PURE__ */ jsxs("div", { children: [toolbar, /* @__PURE__ */ jsxs("div", { children: [navigation, content] })] })
81
+ Layout: ({ toolbar, navigation, content, sidebar }) => /* @__PURE__ */ jsxs("div", { children: [toolbar, /* @__PURE__ */ jsxs("div", { children: [
82
+ navigation,
83
+ content,
84
+ sidebar
85
+ ] })] })
63
86
  };
64
87
  const EditorDesignSystemContext = createContext(nativeEditorComponents);
65
88
  /** Configure a tree once; sibling providers remain independent. */
@@ -67,7 +90,7 @@ function EditorDesignSystemProvider({ components, children }) {
67
90
  const parent = useEditorDesignSystem();
68
91
  const value = useMemo(() => ({
69
92
  ...parent,
70
- ...components
93
+ ...Object.fromEntries(Object.entries(components).filter(([, value]) => value !== void 0))
71
94
  }), [parent, components]);
72
95
  return /* @__PURE__ */ jsx(EditorDesignSystemContext, {
73
96
  value,
@@ -78,8 +101,283 @@ function EditorDesignSystemProvider({ components, children }) {
78
101
  function useEditorDesignSystem() {
79
102
  return useContext(EditorDesignSystemContext);
80
103
  }
104
+ //#endregion
105
+ //#region packages/editor/locale-picker.tsx
106
+ const defaults$2 = {
107
+ title: "Locales",
108
+ empty: "No locales selected",
109
+ clear: "Clear",
110
+ selectAll: (count) => `Select all ${count} ${count === 1 ? "locale" : "locales"}`,
111
+ selected: (count) => `${count} locales selected`,
112
+ trigger: (summary) => `Locales: ${summary}`
113
+ };
114
+ const localeCode = (locale) => locale;
115
+ function LocalePicker({ locales, selectedLocales, onChange, getLocaleLabel = localeCode, labels }) {
116
+ const { Checkbox, Button, LocalePickerLayout } = useEditorDesignSystem();
117
+ const id = useId();
118
+ const [open, setOpen] = useState(false);
119
+ const available = [...new Set(locales)];
120
+ const selected = new Set(selectedLocales);
121
+ const active = available.filter((locale) => selected.has(locale));
122
+ const text = {
123
+ ...defaults$2,
124
+ ...labels
125
+ };
126
+ const summary = active.length === 0 ? text.empty : active.length === 1 ? getLocaleLabel(active[0]) : text.selected(active.length);
127
+ const all = available.length > 0 && active.length === available.length;
128
+ const midpoint = Math.ceil(available.length / 2);
129
+ const columns = [available.slice(0, midpoint), available.slice(midpoint)];
130
+ return /* @__PURE__ */ jsx(LocalePickerLayout, {
131
+ id,
132
+ title: text.title,
133
+ summary,
134
+ triggerLabel: text.trigger(summary),
135
+ open,
136
+ onOpenChange: setOpen,
137
+ controls: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("label", {
138
+ htmlFor: `${id}-all`,
139
+ children: [/* @__PURE__ */ jsx(Checkbox, {
140
+ id: `${id}-all`,
141
+ disabled: available.length === 0,
142
+ checked: all ? true : active.length ? "indeterminate" : false,
143
+ onCheckedChange: (checked) => onChange(checked ? available : [])
144
+ }), text.selectAll(available.length)]
145
+ }), /* @__PURE__ */ jsx(Button, {
146
+ variant: "secondary",
147
+ disabled: active.length === 0,
148
+ onPress: () => onChange([]),
149
+ children: text.clear
150
+ })] }),
151
+ columns: columns.map((column, columnIndex) => column.map((locale, index) => {
152
+ const checkboxId = `${id}-${columnIndex}-${index}`;
153
+ return /* @__PURE__ */ jsxs("label", {
154
+ htmlFor: checkboxId,
155
+ style: {
156
+ display: "flex",
157
+ alignItems: "center",
158
+ gap: 8
159
+ },
160
+ children: [/* @__PURE__ */ jsx(Checkbox, {
161
+ id: checkboxId,
162
+ checked: selected.has(locale),
163
+ onCheckedChange: (checked) => onChange(available.filter((candidate) => candidate === locale ? checked : selected.has(candidate)))
164
+ }), getLocaleLabel(locale)]
165
+ }, locale);
166
+ }))
167
+ });
168
+ }
169
+ //#endregion
170
+ //#region packages/editor/message-preview.tsx
171
+ /** Structural ICU preview: shows every branch, without evaluating values or HTML. */
172
+ function MessagePreview({ message, formatError }) {
173
+ const { PreviewToken } = useEditorDesignSystem();
174
+ const parsed = useMemo(() => {
175
+ try {
176
+ return {
177
+ ast: parse(message, { captureLocation: true }),
178
+ error: null
179
+ };
180
+ } catch (error) {
181
+ return {
182
+ ast: null,
183
+ error: error instanceof Error ? error : new Error(String(error))
184
+ };
185
+ }
186
+ }, [message]);
187
+ if (parsed.error) return /* @__PURE__ */ jsx("p", {
188
+ role: "alert",
189
+ children: formatError ? formatError(parsed.error) : parsed.error.message
190
+ });
191
+ const token = (text, kind, key) => /* @__PURE__ */ jsx(PreviewToken, {
192
+ kind,
193
+ children: /* @__PURE__ */ jsx("bdi", {
194
+ dir: "ltr",
195
+ children: text
196
+ })
197
+ }, key);
198
+ const elements = (ast, prefix) => ast.flatMap((element, index) => {
199
+ const key = `${prefix}-${index}`;
200
+ switch (element.type) {
201
+ case TYPE.literal: return /* @__PURE__ */ jsx("span", { children: element.value }, key);
202
+ case TYPE.pound: return token("#", "pound", key);
203
+ case TYPE.argument:
204
+ case TYPE.number:
205
+ case TYPE.date:
206
+ case TYPE.time: return token(message.slice(element.location.start.offset, element.location.end.offset), TYPE[element.type], key);
207
+ case TYPE.tag: return [
208
+ token(`<${element.value}>`, "tag", `${key}-open`),
209
+ ...elements(element.children, `${key}-children`),
210
+ token(`</${element.value}>`, "tag", `${key}-close`)
211
+ ];
212
+ case TYPE.select:
213
+ case TYPE.plural: {
214
+ const kind = element.type === TYPE.select ? "select" : element.pluralType === "ordinal" ? "selectordinal" : "plural";
215
+ const offset = element.type === TYPE.plural && element.offset ? ` offset:${element.offset}` : "";
216
+ return [
217
+ token(`{${element.value}, ${kind},${offset}`, element.type === TYPE.select ? "selector" : "plural", `${key}-open`),
218
+ ...Object.entries(element.options).flatMap(([selector, option]) => [
219
+ token(`${selector} {`, "selector", `${key}-${selector}`),
220
+ ...elements(option.value, `${key}-${selector}`),
221
+ token("}", "syntax", `${key}-${selector}-close`)
222
+ ]),
223
+ token("}", "syntax", `${key}-close`)
224
+ ];
225
+ }
226
+ }
227
+ });
228
+ return /* @__PURE__ */ jsx("div", {
229
+ dir: "auto",
230
+ style: {
231
+ whiteSpace: "pre-wrap",
232
+ overflowWrap: "anywhere"
233
+ },
234
+ children: elements(parsed.ast, "message")
235
+ });
236
+ }
237
+ //#endregion
238
+ //#region packages/editor/copy-text-button.tsx
239
+ const defaults$1 = {
240
+ copy: (label) => `Copy ${label}`,
241
+ copying: (label) => `Copying ${label}…`,
242
+ copied: (label) => `Copied ${label}`,
243
+ failed: (label) => `Could not copy ${label}`
244
+ };
245
+ async function writeClipboard(value) {
246
+ if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) throw new Error("Clipboard access is unavailable");
247
+ await navigator.clipboard.writeText(value);
248
+ }
249
+ /** Copies exact text; feedback belongs to the current value and mounted control. */
250
+ function CopyTextButton({ value, label, disabled = false, writeText = writeClipboard, onCopy, onError, feedbackDurationMs = 1500, labels }) {
251
+ const { CopyButton } = useEditorDesignSystem();
252
+ const [status, setStatus] = useState("idle");
253
+ const operation = useRef({
254
+ generation: 0,
255
+ pending: false,
256
+ timer: void 0
257
+ });
258
+ useEffect(() => {
259
+ const current = operation.current;
260
+ current.generation++;
261
+ current.pending = false;
262
+ setStatus("idle");
263
+ clearTimeout(current.timer);
264
+ return () => {
265
+ current.generation++;
266
+ clearTimeout(current.timer);
267
+ };
268
+ }, [value, writeText]);
269
+ const copy = async () => {
270
+ const current = operation.current;
271
+ if (disabled || current.pending) return;
272
+ current.pending = true;
273
+ const request = ++current.generation;
274
+ clearTimeout(current.timer);
275
+ setStatus("copying");
276
+ let error;
277
+ try {
278
+ await writeText(value);
279
+ } catch (failure) {
280
+ error = failure instanceof Error ? failure : new Error(String(failure));
281
+ }
282
+ if (request !== current.generation) return;
283
+ current.pending = false;
284
+ setStatus(error ? "error" : "copied");
285
+ current.timer = setTimeout(() => {
286
+ if (request === current.generation) setStatus("idle");
287
+ }, Math.max(0, feedbackDurationMs));
288
+ if (error) onError?.(error, value);
289
+ else onCopy?.(value);
290
+ };
291
+ const text = {
292
+ ...defaults$1,
293
+ ...labels
294
+ };
295
+ const buttonLabel = (status === "copying" ? text.copying : status === "copied" ? text.copied : status === "error" ? text.failed : text.copy)(label);
296
+ return /* @__PURE__ */ jsxs("span", { children: [
297
+ /* @__PURE__ */ jsx(CopyButton, {
298
+ label: buttonLabel,
299
+ status,
300
+ disabled: disabled || status === "copying",
301
+ onPress: () => {
302
+ copy();
303
+ }
304
+ }),
305
+ status === "copied" && /* @__PURE__ */ jsx("output", { children: text.copied(label) }),
306
+ status === "error" && /* @__PURE__ */ jsx("span", {
307
+ role: "alert",
308
+ children: text.failed(label)
309
+ })
310
+ ] });
311
+ }
312
+ //#endregion
313
+ //#region packages/editor/message-context.tsx
314
+ const defaults = {
315
+ title: "Message context",
316
+ id: "Message ID",
317
+ description: "Description",
318
+ catalogs: "Source catalogs",
319
+ locations: "Source locations"
320
+ };
321
+ function locationText(location) {
322
+ const start = location.start === void 0 ? "" : `:${location.start}`;
323
+ const end = location.end === void 0 ? "" : `${location.start === void 0 ? ":" : "–"}${location.end}`;
324
+ return `${location.file}${start}${end}`;
325
+ }
326
+ function MessageContext({ message, labels, copyId = true, copyOptions, renderLocation = locationText }) {
327
+ const { Metadata } = useEditorDesignSystem();
328
+ const text = {
329
+ ...defaults,
330
+ ...labels
331
+ };
332
+ return /* @__PURE__ */ jsx(Metadata, {
333
+ label: text.title,
334
+ children: message && /* @__PURE__ */ jsxs("dl", { children: [
335
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: text.id }), /* @__PURE__ */ jsxs("dd", { children: [/* @__PURE__ */ jsx("code", { children: message.id }), copyId && /* @__PURE__ */ jsx(CopyTextButton, {
336
+ ...copyOptions,
337
+ value: message.id,
338
+ label: text.id
339
+ })] })] }),
340
+ message.description && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: text.description }), /* @__PURE__ */ jsx("dd", { children: message.description })] }),
341
+ !!message.catalogs?.length && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: text.catalogs }), /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx("ul", { children: message.catalogs.map((catalog, index) => /* @__PURE__ */ jsx("li", { children: catalog }, index)) }) })] }),
342
+ !!message.locations?.length && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: text.locations }), /* @__PURE__ */ jsx("dd", { children: /* @__PURE__ */ jsx("ul", { children: message.locations.map((location, index) => /* @__PURE__ */ jsx("li", { children: renderLocation(location) }, index)) }) })] })
343
+ ] })
344
+ });
345
+ }
346
+ //#endregion
347
+ //#region packages/editor/ui.tsx
348
+ const defaultLabels = {
349
+ search: "Search messages",
350
+ messages: "Messages",
351
+ source: "Source message",
352
+ noMessages: "No matching messages",
353
+ noSelection: "Select a message",
354
+ loading: "Loading messages…",
355
+ copySource: "Copy source",
356
+ reset: "Reset",
357
+ save: "Save translation",
358
+ saving: "Saving…",
359
+ saved: "Translation saved.",
360
+ unsaved: "Unsaved changes",
361
+ unchanged: "No unsaved changes",
362
+ validation: {
363
+ empty: "Enter a translation before saving.",
364
+ "invalid-source": "The source contains invalid ICU syntax.",
365
+ "invalid-translation": "The translation contains invalid ICU syntax.",
366
+ structure: "Preserve ICU arguments, tags, formatting styles, and selector branches."
367
+ }
368
+ };
369
+ function resolveLabels(labels) {
370
+ return {
371
+ ...defaultLabels,
372
+ ...labels,
373
+ validation: {
374
+ ...defaultLabels.validation,
375
+ ...labels?.validation
376
+ }
377
+ };
378
+ }
81
379
  /** Controlled list: it never filters, paginates, fetches, or changes selection itself. */
82
- function MessageList({ messages, selectedId, onSelect, search, loading = false, pagination, labels }) {
380
+ function MessageList({ messages, selectedId, onSelect, search, loading = false, pagination, listSummary, renderMessage, renderMessageActions, labels }) {
83
381
  const { TextInput, MessageRow } = useEditorDesignSystem();
84
382
  const text = resolveLabels(labels);
85
383
  const searchId = useId();
@@ -96,16 +394,20 @@ function MessageList({ messages, selectedId, onSelect, search, loading = false,
96
394
  value: search.value,
97
395
  onValueChange: search.onValueChange
98
396
  })] }),
397
+ listSummary,
99
398
  loading && /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("output", { children: text.loading }) }),
100
- /* @__PURE__ */ jsx("ul", { children: messages.map((message) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(MessageRow, {
101
- selected: message.id === selectedId,
102
- onSelect: () => onSelect(message.id),
103
- children: [
104
- /* @__PURE__ */ jsx("span", { children: message.defaultMessage }),
105
- " ",
106
- /* @__PURE__ */ jsx("code", { children: message.id })
107
- ]
108
- }) }, message.id)) }),
399
+ /* @__PURE__ */ jsx("ul", { children: messages.map((message) => {
400
+ const state = { selected: message.id === selectedId };
401
+ return /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx(MessageRow, {
402
+ selected: state.selected,
403
+ onSelect: () => onSelect(message.id),
404
+ children: renderMessage ? renderMessage(message, state) : /* @__PURE__ */ jsxs(Fragment, { children: [
405
+ /* @__PURE__ */ jsx("span", { children: message.defaultMessage }),
406
+ " ",
407
+ /* @__PURE__ */ jsx("code", { children: message.id })
408
+ ] })
409
+ }), renderMessageActions?.(message, state)] }, message.id);
410
+ }) }),
109
411
  !loading && messages.length === 0 && /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("output", { children: text.noMessages }) }),
110
412
  pagination
111
413
  ]
@@ -183,30 +485,40 @@ function TranslationField({ locale, label = locale, source, draft, onSave, actio
183
485
  });
184
486
  }
185
487
  /** A stateless composition over caller-owned navigation and locale drafts. */
186
- function TranslationEditorView({ selectedMessage, translations, filters, context, sourcePreview, notice, labels, ...list }) {
488
+ function TranslationEditorView({ selectedMessage, selectedId = selectedMessage?.id, translations, filters, context, sourcePreview, notice, sidebar, emptyState, renderTranslations, renderContent, labels, ...list }) {
187
489
  const { Layout } = useEditorDesignSystem();
188
490
  const text = resolveLabels(labels);
491
+ const fields = selectedMessage ? translations.map((translation) => /* @__PURE__ */ createElement(TranslationField, {
492
+ ...translation,
493
+ key: `${selectedMessage.id}:${translation.locale}`,
494
+ source: selectedMessage.defaultMessage,
495
+ labels: {
496
+ ...labels,
497
+ ...translation.labels,
498
+ validation: {
499
+ ...labels?.validation,
500
+ ...translation.labels?.validation
501
+ }
502
+ }
503
+ })) : null;
504
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [notice, selectedMessage ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SourceMessage, {
505
+ message: selectedMessage,
506
+ preview: sourcePreview,
507
+ context,
508
+ labels
509
+ }), renderTranslations ? renderTranslations(fields) : fields] }) : emptyState !== void 0 ? emptyState : /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("output", { children: text.noSelection }) })] });
189
510
  return /* @__PURE__ */ jsx(Layout, {
190
511
  toolbar: filters,
191
512
  navigation: /* @__PURE__ */ jsx(MessageList, {
192
513
  ...list,
193
- selectedId: selectedMessage?.id,
514
+ selectedId,
194
515
  labels
195
516
  }),
196
- content: /* @__PURE__ */ jsxs(Fragment, { children: [notice, selectedMessage ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SourceMessage, {
197
- message: selectedMessage,
198
- preview: sourcePreview,
199
- context,
200
- labels
201
- }), translations.map((translation) => /* @__PURE__ */ createElement(TranslationField, {
202
- ...translation,
203
- key: `${selectedMessage.id}:${translation.locale}`,
204
- source: selectedMessage.defaultMessage,
205
- labels
206
- }))] }) : /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("output", { children: text.noSelection }) })] })
517
+ content: renderContent ? renderContent(content) : content,
518
+ sidebar
207
519
  });
208
520
  }
209
521
  //#endregion
210
- export { EditorDesignSystemProvider, MessageList, SourceMessage, TranslationEditorView, TranslationField, nativeEditorComponents, useEditorDesignSystem };
522
+ export { CopyTextButton, EditorDesignSystemProvider, LocalePicker, MessageContext, MessageList, MessagePreview, SourceMessage, TranslationEditorView, TranslationField, nativeEditorComponents, useEditorDesignSystem };
211
523
 
212
524
  //# sourceMappingURL=ui.js.map
package/ui.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ui.js","names":[],"sources":["../ui.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n useId,\n useMemo,\n type ComponentType,\n type ReactNode,\n} from 'react'\nimport type {\n EditorMessage,\n TranslationDraftState,\n} from '#packages/editor/workflow.js'\nimport type {TranslationValidationError} from '#packages/editor/validation.js'\n\nexport interface EditorButtonProps {\n children: ReactNode\n /** Called once per activation, without a DOM event; disabled controls must not call it. */\n onPress: () => void\n disabled?: boolean\n variant: 'primary' | 'secondary'\n}\nexport interface EditorInputProps {\n id: string\n value: string\n /** Reports the complete next string value; never a DOM event. */\n onValueChange: (value: string) => void\n disabled?: boolean\n 'aria-invalid'?: boolean\n 'aria-describedby'?: string\n}\nexport interface EditorTextInputProps extends EditorInputProps {\n type: 'text' | 'search'\n}\nexport interface EditorTextAreaProps extends EditorInputProps {\n /** Visible rows when supported by the control; defaults to six. */\n rows?: number\n}\nexport interface EditorMessageRowProps {\n children: ReactNode\n selected: boolean\n /** Activates this row without changing controlled selection itself. */\n onSelect: () => void\n}\nexport interface EditorPanelProps {\n children: ReactNode\n label: string\n kind: 'source' | 'translation'\n}\nexport interface EditorLayoutProps {\n toolbar: ReactNode\n navigation: ReactNode\n content: ReactNode\n}\n/** Define adapters outside render so controls retain focus across edits. */\nexport interface EditorComponents {\n Button: ComponentType<EditorButtonProps>\n TextInput: ComponentType<EditorTextInputProps>\n TextArea: ComponentType<EditorTextAreaProps>\n MessageRow: ComponentType<EditorMessageRowProps>\n Panel: ComponentType<EditorPanelProps>\n Layout: ComponentType<EditorLayoutProps>\n}\nexport interface EditorLabels {\n search: string\n messages: string\n source: string\n noMessages: string\n noSelection: string\n loading: string\n copySource: string\n reset: string\n save: string\n saving: string\n saved: string\n unsaved: string\n unchanged: string\n validation: Record<TranslationValidationError, string>\n}\nconst defaultLabels: EditorLabels = {\n search: 'Search messages',\n messages: 'Messages',\n source: 'Source message',\n noMessages: 'No matching messages',\n noSelection: 'Select a message',\n loading: 'Loading messages…',\n copySource: 'Copy source',\n reset: 'Reset',\n save: 'Save translation',\n saving: 'Saving…',\n saved: 'Translation saved.',\n unsaved: 'Unsaved changes',\n unchanged: 'No unsaved changes',\n validation: {\n empty: 'Enter a translation before saving.',\n 'invalid-source': 'The source contains invalid ICU syntax.',\n 'invalid-translation': 'The translation contains invalid ICU syntax.',\n structure:\n 'Preserve ICU arguments, tags, formatting styles, and selector branches.',\n },\n}\nexport type EditorLabelOverrides = Partial<Omit<EditorLabels, 'validation'>> & {\n validation?: Partial<EditorLabels['validation']>\n}\nfunction resolveLabels(labels?: EditorLabelOverrides): EditorLabels {\n return {\n ...defaultLabels,\n ...labels,\n validation: {...defaultLabels.validation, ...labels?.validation},\n }\n}\n/** Unstyled native controls; no CSS, icons, or localization provider is required. */\nexport const nativeEditorComponents: EditorComponents = {\n Button: ({children, onPress, disabled}) => (\n <button type=\"button\" disabled={disabled} onClick={() => onPress()}>\n {children}\n </button>\n ),\n TextInput: ({onValueChange, ...props}) => (\n <input {...props} onChange={event => onValueChange(event.target.value)} />\n ),\n TextArea: ({onValueChange, rows = 6, ...props}) => (\n <textarea\n {...props}\n rows={rows}\n onChange={event => onValueChange(event.target.value)}\n />\n ),\n MessageRow: ({children, selected, onSelect}) => (\n <button\n type=\"button\"\n aria-current={selected ? 'true' : undefined}\n onClick={() => onSelect()}\n >\n {children}\n </button>\n ),\n Panel: ({children, label}) => (\n <section aria-label={label}>{children}</section>\n ),\n Layout: ({toolbar, navigation, content}) => (\n <div>\n {toolbar}\n <div>\n {navigation}\n {content}\n </div>\n </div>\n ),\n}\nconst EditorDesignSystemContext = createContext<Readonly<EditorComponents>>(\n nativeEditorComponents\n)\n\nexport interface EditorDesignSystemProviderProps {\n /** Overrides inherit unspecified components from the nearest provider. */\n components: Partial<EditorComponents>\n children: ReactNode\n}\n/** Configure a tree once; sibling providers remain independent. */\nexport function EditorDesignSystemProvider({\n components,\n children,\n}: EditorDesignSystemProviderProps): ReactNode {\n const parent = useEditorDesignSystem()\n const value = useMemo(\n () => ({...parent, ...components}),\n [parent, components]\n )\n return (\n <EditorDesignSystemContext value={value}>\n {children}\n </EditorDesignSystemContext>\n )\n}\n/** Read the resolved controls, including native defaults outside a provider. */\nexport function useEditorDesignSystem(): Readonly<EditorComponents> {\n return useContext(EditorDesignSystemContext)\n}\ninterface ViewOptions {\n labels?: EditorLabelOverrides\n}\nexport type EditorViewMessage = Pick<\n EditorMessage,\n 'id' | 'defaultMessage' | 'description'\n>\nexport interface EditorSearch {\n value: string\n onValueChange: (value: string) => void\n}\nexport interface MessageListProps extends ViewOptions {\n messages: readonly EditorViewMessage[]\n selectedId?: string\n onSelect: (id: string) => void\n search?: EditorSearch\n loading?: boolean\n pagination?: ReactNode\n}\n/** Controlled list: it never filters, paginates, fetches, or changes selection itself. */\nexport function MessageList({\n messages,\n selectedId,\n onSelect,\n search,\n loading = false,\n pagination,\n labels,\n}: MessageListProps): ReactNode {\n const {TextInput, MessageRow} = useEditorDesignSystem()\n const text = resolveLabels(labels)\n const searchId = useId()\n return (\n <nav aria-label={text.messages} aria-busy={loading}>\n {search && (\n <div>\n <label htmlFor={searchId}>{text.search}</label>\n <TextInput\n id={searchId}\n type=\"search\"\n value={search.value}\n onValueChange={search.onValueChange}\n />\n </div>\n )}\n {loading && (\n <p>\n <output>{text.loading}</output>\n </p>\n )}\n <ul>\n {messages.map(message => (\n <li key={message.id}>\n <MessageRow\n selected={message.id === selectedId}\n onSelect={() => onSelect(message.id)}\n >\n <span>{message.defaultMessage}</span> <code>{message.id}</code>\n </MessageRow>\n </li>\n ))}\n </ul>\n {!loading && messages.length === 0 && (\n <p>\n <output>{text.noMessages}</output>\n </p>\n )}\n {pagination}\n </nav>\n )\n}\nexport interface SourceMessageProps extends ViewOptions {\n message: EditorViewMessage\n preview?: ReactNode\n context?: ReactNode\n}\nexport function SourceMessage({\n message,\n preview,\n context,\n labels,\n}: SourceMessageProps): ReactNode {\n const {Panel} = useEditorDesignSystem()\n const text = resolveLabels(labels)\n return (\n <Panel kind=\"source\" label={text.source}>\n <h2>{text.source}</h2>\n <code>{message.id}</code>\n {preview ?? <pre>{message.defaultMessage}</pre>}\n {message.description && <p>{message.description}</p>}\n {context}\n </Panel>\n )\n}\nexport type TranslationFieldDraft = Pick<\n TranslationDraftState,\n | 'value'\n | 'validationError'\n | 'changed'\n | 'isSaving'\n | 'saveError'\n | 'saved'\n | 'setTranslation'\n | 'reset'\n>\nexport interface TranslationFieldProps extends ViewOptions {\n locale: string\n /** Human-readable label; defaults to the locale identifier. */\n label?: string\n source: string\n draft: TranslationFieldDraft\n /** The caller owns confirmation, context, persistence, and receipt presentation. */\n onSave?: () => void\n /** Overrides the default copy/reset/save actions, including with null. */\n actions?: ReactNode\n preview?: ReactNode\n}\nexport function TranslationField({\n locale,\n label = locale,\n source,\n draft,\n onSave,\n actions,\n preview,\n labels,\n}: TranslationFieldProps): ReactNode {\n const {Panel, TextArea, Button} = useEditorDesignSystem()\n const text = resolveLabels(labels)\n const id = useId()\n const errorId = `${id}-error`\n const statusId = `${id}-status`\n const validation = draft.validationError\n ? text.validation[draft.validationError]\n : null\n const error = validation ?? draft.saveError?.message\n return (\n <Panel kind=\"translation\" label={label}>\n <label htmlFor={id}>{label}</label>\n <TextArea\n id={id}\n value={draft.value}\n onValueChange={draft.setTranslation}\n aria-invalid={!!validation}\n aria-describedby={error ? `${errorId} ${statusId}` : statusId}\n />\n {error && (\n <p id={errorId} role=\"alert\">\n {error}\n </p>\n )}\n <p>\n <output id={statusId}>\n {draft.isSaving\n ? text.saving\n : draft.saved\n ? text.saved\n : draft.changed\n ? text.unsaved\n : text.unchanged}\n </output>\n </p>\n {preview}\n {actions !== undefined ? (\n actions\n ) : (\n <div>\n <Button\n variant=\"secondary\"\n disabled={draft.isSaving}\n onPress={() => draft.setTranslation(source)}\n >\n {text.copySource}\n </Button>\n <Button\n variant=\"secondary\"\n disabled={!draft.changed || draft.isSaving}\n onPress={draft.reset}\n >\n {text.reset}\n </Button>\n {onSave && (\n <Button\n variant=\"primary\"\n disabled={\n !draft.changed || !!draft.validationError || draft.isSaving\n }\n onPress={onSave}\n >\n {draft.isSaving ? text.saving : text.save}\n </Button>\n )}\n </div>\n )}\n </Panel>\n )\n}\nexport type EditorTranslation = Omit<TranslationFieldProps, 'source' | 'labels'>\nexport interface TranslationEditorViewProps extends Omit<\n MessageListProps,\n 'selectedId'\n> {\n /** May be outside the loaded list, e.g. during a server-side page change. */\n selectedMessage?: EditorViewMessage\n translations: readonly EditorTranslation[]\n filters?: ReactNode\n context?: ReactNode\n sourcePreview?: ReactNode\n notice?: ReactNode\n}\n/** A stateless composition over caller-owned navigation and locale drafts. */\nexport function TranslationEditorView({\n selectedMessage,\n translations,\n filters,\n context,\n sourcePreview,\n notice,\n labels,\n ...list\n}: TranslationEditorViewProps): ReactNode {\n const {Layout} = useEditorDesignSystem()\n const text = resolveLabels(labels)\n return (\n <Layout\n toolbar={filters}\n navigation={\n <MessageList\n {...list}\n selectedId={selectedMessage?.id}\n labels={labels}\n />\n }\n content={\n <>\n {notice}\n {selectedMessage ? (\n <>\n <SourceMessage\n message={selectedMessage}\n preview={sourcePreview}\n context={context}\n labels={labels}\n />\n {translations.map(translation => (\n <TranslationField\n {...translation}\n key={`${selectedMessage.id}:${translation.locale}`}\n source={selectedMessage.defaultMessage}\n labels={labels}\n />\n ))}\n </>\n ) : (\n <p>\n <output>{text.noSelection}</output>\n </p>\n )}\n </>\n }\n />\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ui.js","names":["defaults","defaults"],"sources":["../design-system.tsx","../locale-picker.tsx","../message-preview.tsx","../copy-text-button.tsx","../message-context.tsx","../ui.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n type ComponentType,\n type ReactNode,\n} from 'react'\nexport interface EditorButtonProps {\n children: ReactNode\n /** Called once per activation, without a DOM event; disabled controls must not call it. */\n onPress: () => void\n disabled?: boolean\n variant: 'primary' | 'secondary'\n}\nexport interface EditorInputProps {\n id: string\n value: string\n /** Reports the complete next string value; never a DOM event. */\n onValueChange: (value: string) => void\n disabled?: boolean\n 'aria-invalid'?: boolean\n 'aria-describedby'?: string\n}\nexport interface EditorTextInputProps extends EditorInputProps {\n type: 'text' | 'search'\n}\nexport interface EditorTextAreaProps extends EditorInputProps {\n /** Visible rows when supported by the control; defaults to six. */\n rows?: number\n}\nexport interface EditorMessageRowProps {\n children: ReactNode\n selected: boolean\n /** Activates this row without changing controlled selection itself. */\n onSelect: () => void\n}\nexport interface EditorPanelProps {\n children: ReactNode\n label: string\n kind: 'source' | 'translation'\n}\nexport interface EditorLayoutProps {\n toolbar: ReactNode\n navigation: ReactNode\n content: ReactNode\n /** Optional secondary content, arranged by the design-system layout. */\n sidebar?: ReactNode\n}\n/** Define adapters outside render so controls retain focus across edits. */\nexport interface EditorComponents extends Partial<EditorToolComponents> {\n Button: ComponentType<EditorButtonProps>\n TextInput: ComponentType<EditorTextInputProps>\n TextArea: ComponentType<EditorTextAreaProps>\n MessageRow: ComponentType<EditorMessageRowProps>\n Panel: ComponentType<EditorPanelProps>\n Layout: ComponentType<EditorLayoutProps>\n}\nexport interface EditorCheckboxProps {\n id: string\n checked: boolean | 'indeterminate'\n disabled?: boolean\n onCheckedChange: (checked: boolean) => void\n}\nexport interface EditorLocalePickerLayoutProps {\n id: string\n title: string\n summary: string\n triggerLabel: string\n open: boolean\n onOpenChange: (open: boolean) => void\n controls: ReactNode\n /** Contiguous, balanced columns in the caller's locale order. */\n columns: readonly ReactNode[]\n}\nexport interface EditorPreviewTokenProps {\n children: ReactNode\n kind:\n | 'argument'\n | 'number'\n | 'date'\n | 'time'\n | 'tag'\n | 'selector'\n | 'plural'\n | 'pound'\n | 'syntax'\n}\nexport type EditorCopyStatus = 'idle' | 'copying' | 'copied' | 'error'\nexport interface EditorCopyButtonProps {\n label: string\n status: EditorCopyStatus\n disabled: boolean\n onPress: () => void\n}\nexport interface EditorMetadataProps {\n label: string\n children: ReactNode\n}\n/** Optional additions preserve existing complete design-system registries. */\nexport interface EditorToolComponents {\n Checkbox: ComponentType<EditorCheckboxProps>\n LocalePickerLayout: ComponentType<EditorLocalePickerLayoutProps>\n PreviewToken: ComponentType<EditorPreviewTokenProps>\n CopyButton: ComponentType<EditorCopyButtonProps>\n Metadata: ComponentType<EditorMetadataProps>\n}\nexport type ResolvedEditorComponents = Required<EditorComponents>\n/** Unstyled native controls; no CSS, icons, or localization provider is required. */\nexport const nativeEditorComponents: ResolvedEditorComponents = {\n Checkbox: function NativeCheckbox({checked, onCheckedChange, ...props}) {\n const ref = useRef<HTMLInputElement>(null)\n useEffect(() => {\n if (ref.current) ref.current.indeterminate = checked === 'indeterminate'\n }, [checked])\n return (\n <input\n {...props}\n ref={ref}\n type=\"checkbox\"\n checked={checked === true}\n aria-checked={checked === 'indeterminate' ? 'mixed' : checked}\n onChange={event => onCheckedChange(event.target.checked)}\n />\n )\n },\n LocalePickerLayout: ({\n id,\n title,\n summary,\n triggerLabel,\n open,\n onOpenChange,\n controls,\n columns,\n }) => (\n <div>\n <button\n type=\"button\"\n aria-label={triggerLabel}\n aria-expanded={open}\n aria-controls={id}\n onClick={() => onOpenChange(!open)}\n >\n {summary}\n </button>\n <fieldset id={id} hidden={!open}>\n <legend>{title}</legend>\n {controls}\n <div\n style={{\n display: 'grid',\n gridTemplateColumns:\n 'repeat(auto-fit, minmax(min(100%, 16rem), 1fr))',\n maxHeight: '60vh',\n overflowY: 'auto',\n }}\n >\n {columns.map((column, index) => (\n <div key={index}>{column}</div>\n ))}\n </div>\n </fieldset>\n </div>\n ),\n PreviewToken: ({children}) => <code>{children}</code>,\n CopyButton: ({label, onPress, disabled}) => (\n <button type=\"button\" disabled={disabled} onClick={() => onPress()}>\n {label}\n </button>\n ),\n Metadata: ({label, children}) => <aside aria-label={label}>{children}</aside>,\n Button: ({children, onPress, disabled}) => (\n <button type=\"button\" disabled={disabled} onClick={() => onPress()}>\n {children}\n </button>\n ),\n TextInput: ({onValueChange, ...props}) => (\n <input {...props} onChange={event => onValueChange(event.target.value)} />\n ),\n TextArea: ({onValueChange, rows = 6, ...props}) => (\n <textarea\n {...props}\n rows={rows}\n onChange={event => onValueChange(event.target.value)}\n />\n ),\n MessageRow: ({children, selected, onSelect}) => (\n <button\n type=\"button\"\n aria-current={selected ? 'true' : undefined}\n onClick={() => onSelect()}\n >\n {children}\n </button>\n ),\n Panel: ({children, label}) => (\n <section aria-label={label}>{children}</section>\n ),\n Layout: ({toolbar, navigation, content, sidebar}) => (\n <div>\n {toolbar}\n <div>\n {navigation}\n {content}\n {sidebar}\n </div>\n </div>\n ),\n}\nconst EditorDesignSystemContext = createContext<\n Readonly<ResolvedEditorComponents>\n>(nativeEditorComponents)\n\nexport interface EditorDesignSystemProviderProps {\n /** Overrides inherit unspecified components from the nearest provider. */\n components: Partial<EditorComponents>\n children: ReactNode\n}\n/** Configure a tree once; sibling providers remain independent. */\nexport function EditorDesignSystemProvider({\n components,\n children,\n}: EditorDesignSystemProviderProps): ReactNode {\n const parent = useEditorDesignSystem()\n const value = useMemo(\n () => ({\n ...parent,\n ...Object.fromEntries(\n Object.entries(components).filter(([, value]) => value !== undefined)\n ),\n }),\n [parent, components]\n )\n return (\n <EditorDesignSystemContext value={value}>\n {children}\n </EditorDesignSystemContext>\n )\n}\n/** Read the resolved controls, including native defaults outside a provider. */\nexport function useEditorDesignSystem(): Readonly<ResolvedEditorComponents> {\n return useContext(EditorDesignSystemContext)\n}\n","import {useId, useState, type ReactNode} from 'react'\nimport {useEditorDesignSystem} from '#packages/editor/design-system.js'\n\nexport interface LocalePickerLabels {\n title: string\n empty: string\n clear: string\n selectAll: (count: number) => string\n selected: (count: number) => string\n trigger: (summary: string) => string\n}\nexport interface LocalePickerProps {\n locales: readonly string[]\n selectedLocales: readonly string[]\n /** Reports unique available locales in their input order; never mutates selection. */\n onChange: (locales: string[]) => void\n /** Defaults to the locale code; consumers choose the display language. */\n getLocaleLabel?: (locale: string) => string\n labels?: Partial<LocalePickerLabels>\n}\nconst defaults: LocalePickerLabels = {\n title: 'Locales',\n empty: 'No locales selected',\n clear: 'Clear',\n selectAll: count =>\n `Select all ${count} ${count === 1 ? 'locale' : 'locales'}`,\n selected: count => `${count} locales selected`,\n trigger: summary => `Locales: ${summary}`,\n}\nconst localeCode = (locale: string): string => locale\n\nexport function LocalePicker({\n locales,\n selectedLocales,\n onChange,\n getLocaleLabel = localeCode,\n labels,\n}: LocalePickerProps): ReactNode {\n const {Checkbox, Button, LocalePickerLayout} = useEditorDesignSystem()\n const id = useId()\n const [open, setOpen] = useState(false)\n const available = [...new Set(locales)]\n const selected = new Set(selectedLocales)\n const active = available.filter(locale => selected.has(locale))\n const text = {...defaults, ...labels}\n const summary =\n active.length === 0\n ? text.empty\n : active.length === 1\n ? getLocaleLabel(active[0]!)\n : text.selected(active.length)\n const all = available.length > 0 && active.length === available.length\n const midpoint = Math.ceil(available.length / 2)\n const columns = [available.slice(0, midpoint), available.slice(midpoint)]\n return (\n <LocalePickerLayout\n id={id}\n title={text.title}\n summary={summary}\n triggerLabel={text.trigger(summary)}\n open={open}\n onOpenChange={setOpen}\n controls={\n <div>\n <label htmlFor={`${id}-all`}>\n <Checkbox\n id={`${id}-all`}\n disabled={available.length === 0}\n checked={all ? true : active.length ? 'indeterminate' : false}\n onCheckedChange={checked => onChange(checked ? available : [])}\n />\n {text.selectAll(available.length)}\n </label>\n <Button\n variant=\"secondary\"\n disabled={active.length === 0}\n onPress={() => onChange([])}\n >\n {text.clear}\n </Button>\n </div>\n }\n columns={columns.map((column, columnIndex) =>\n column.map((locale, index) => {\n const checkboxId = `${id}-${columnIndex}-${index}`\n return (\n <label\n key={locale}\n htmlFor={checkboxId}\n style={{display: 'flex', alignItems: 'center', gap: 8}}\n >\n <Checkbox\n id={checkboxId}\n checked={selected.has(locale)}\n onCheckedChange={checked =>\n onChange(\n available.filter(candidate =>\n candidate === locale ? checked : selected.has(candidate)\n )\n )\n }\n />\n {getLocaleLabel(locale)}\n </label>\n )\n })\n )}\n />\n )\n}\n","import {\n parse,\n TYPE,\n type MessageFormatElement,\n} from '@formatjs/icu-messageformat-parser'\nimport {useMemo, type ReactNode} from 'react'\nimport {\n useEditorDesignSystem,\n type EditorPreviewTokenProps,\n} from '#packages/editor/design-system.js'\n\nexport interface MessagePreviewProps {\n message: string\n /** Customize a parser error without suppressing the alert semantics. */\n formatError?: (error: Error) => ReactNode\n}\n\n/** Structural ICU preview: shows every branch, without evaluating values or HTML. */\nexport function MessagePreview({\n message,\n formatError,\n}: MessagePreviewProps): ReactNode {\n const {PreviewToken} = useEditorDesignSystem()\n const parsed = useMemo(() => {\n try {\n return {ast: parse(message, {captureLocation: true}), error: null}\n } catch (error) {\n return {\n ast: null,\n error: error instanceof Error ? error : new Error(String(error)),\n }\n }\n }, [message])\n if (parsed.error)\n return (\n <p role=\"alert\">\n {formatError ? formatError(parsed.error) : parsed.error.message}\n </p>\n )\n const token = (\n text: string,\n kind: EditorPreviewTokenProps['kind'],\n key: string\n ): ReactNode => (\n <PreviewToken key={key} kind={kind}>\n <bdi dir=\"ltr\">{text}</bdi>\n </PreviewToken>\n )\n const elements = (ast: MessageFormatElement[], prefix: string): ReactNode[] =>\n ast.flatMap((element, index) => {\n const key = `${prefix}-${index}`\n switch (element.type) {\n case TYPE.literal:\n return <span key={key}>{element.value}</span>\n case TYPE.pound:\n return token('#', 'pound', key)\n case TYPE.argument:\n case TYPE.number:\n case TYPE.date:\n case TYPE.time:\n return token(\n message.slice(\n element.location!.start.offset,\n element.location!.end.offset\n ),\n TYPE[element.type] as 'argument' | 'number' | 'date' | 'time',\n key\n )\n case TYPE.tag:\n return [\n token(`<${element.value}>`, 'tag', `${key}-open`),\n ...elements(element.children, `${key}-children`),\n token(`</${element.value}>`, 'tag', `${key}-close`),\n ]\n case TYPE.select:\n case TYPE.plural: {\n const kind =\n element.type === TYPE.select\n ? 'select'\n : element.pluralType === 'ordinal'\n ? 'selectordinal'\n : 'plural'\n const offset =\n element.type === TYPE.plural && element.offset\n ? ` offset:${element.offset}`\n : ''\n return [\n token(\n `{${element.value}, ${kind},${offset}`,\n element.type === TYPE.select ? 'selector' : 'plural',\n `${key}-open`\n ),\n ...Object.entries(element.options).flatMap(([selector, option]) => [\n token(`${selector} {`, 'selector', `${key}-${selector}`),\n ...elements(option.value, `${key}-${selector}`),\n token('}', 'syntax', `${key}-${selector}-close`),\n ]),\n token('}', 'syntax', `${key}-close`),\n ]\n }\n }\n })\n return (\n <div dir=\"auto\" style={{whiteSpace: 'pre-wrap', overflowWrap: 'anywhere'}}>\n {elements(parsed.ast!, 'message')}\n </div>\n )\n}\n","import {useEffect, useRef, useState, type ReactNode} from 'react'\nimport {\n useEditorDesignSystem,\n type EditorCopyStatus,\n} from '#packages/editor/design-system.js'\n\nexport interface CopyTextLabels {\n copy: (label: string) => string\n copying: (label: string) => string\n copied: (label: string) => string\n failed: (label: string) => string\n}\nexport interface CopyTextButtonProps {\n value: string\n label: string\n disabled?: boolean\n /** Injectable clipboard boundary; rejects when the write fails. */\n writeText?: (value: string) => Promise<void>\n onCopy?: (value: string) => void\n onError?: (error: Error, value: string) => void\n feedbackDurationMs?: number\n labels?: Partial<CopyTextLabels>\n}\nconst defaults: CopyTextLabels = {\n copy: label => `Copy ${label}`,\n copying: label => `Copying ${label}…`,\n copied: label => `Copied ${label}`,\n failed: label => `Could not copy ${label}`,\n}\nasync function writeClipboard(value: string): Promise<void> {\n if (typeof navigator === 'undefined' || !navigator.clipboard?.writeText) {\n throw new Error('Clipboard access is unavailable')\n }\n await navigator.clipboard.writeText(value)\n}\n\n/** Copies exact text; feedback belongs to the current value and mounted control. */\nexport function CopyTextButton({\n value,\n label,\n disabled = false,\n writeText = writeClipboard,\n onCopy,\n onError,\n feedbackDurationMs = 1500,\n labels,\n}: CopyTextButtonProps): ReactNode {\n const {CopyButton} = useEditorDesignSystem()\n const [status, setStatus] = useState<EditorCopyStatus>('idle')\n const operation = useRef({\n generation: 0,\n pending: false,\n timer: undefined as ReturnType<typeof setTimeout> | undefined,\n })\n useEffect(() => {\n const current = operation.current\n current.generation++\n current.pending = false\n setStatus('idle')\n clearTimeout(current.timer)\n return () => {\n current.generation++\n clearTimeout(current.timer)\n }\n }, [value, writeText])\n const copy = async (): Promise<void> => {\n const current = operation.current\n if (disabled || current.pending) return\n current.pending = true\n const request = ++current.generation\n clearTimeout(current.timer)\n setStatus('copying')\n let error: Error | undefined\n try {\n await writeText(value)\n } catch (failure) {\n error = failure instanceof Error ? failure : new Error(String(failure))\n }\n if (request !== current.generation) return\n current.pending = false\n setStatus(error ? 'error' : 'copied')\n current.timer = setTimeout(\n () => {\n if (request === current.generation) setStatus('idle')\n },\n Math.max(0, feedbackDurationMs)\n )\n if (error) onError?.(error, value)\n else onCopy?.(value)\n }\n const text = {...defaults, ...labels}\n const buttonLabel = (\n status === 'copying'\n ? text.copying\n : status === 'copied'\n ? text.copied\n : status === 'error'\n ? text.failed\n : text.copy\n )(label)\n return (\n <span>\n <CopyButton\n label={buttonLabel}\n status={status}\n disabled={disabled || status === 'copying'}\n onPress={() => {\n void copy()\n }}\n />\n {status === 'copied' && <output>{text.copied(label)}</output>}\n {status === 'error' && <span role=\"alert\">{text.failed(label)}</span>}\n </span>\n )\n}\n","import type {ReactNode} from 'react'\nimport {useEditorDesignSystem} from '#packages/editor/design-system.js'\nimport {\n CopyTextButton,\n type CopyTextButtonProps,\n} from '#packages/editor/copy-text-button.js'\nimport type {EditorMessage, SourceLocation} from '#packages/editor/workflow.js'\n\nexport interface MessageContextLabels {\n title: string\n id: string\n description: string\n catalogs: string\n locations: string\n}\nexport interface MessageContextProps {\n message?: Pick<\n EditorMessage,\n 'id' | 'description' | 'catalogs' | 'locations'\n > | null\n labels?: Partial<MessageContextLabels>\n copyId?: boolean\n copyOptions?: Pick<\n CopyTextButtonProps,\n 'writeText' | 'onCopy' | 'onError' | 'labels' | 'feedbackDurationMs'\n >\n /** Default locations are plain text, never file URLs or HTML. */\n renderLocation?: (location: SourceLocation) => ReactNode\n}\nconst defaults: MessageContextLabels = {\n title: 'Message context',\n id: 'Message ID',\n description: 'Description',\n catalogs: 'Source catalogs',\n locations: 'Source locations',\n}\nfunction locationText(location: SourceLocation): string {\n const start = location.start === undefined ? '' : `:${location.start}`\n const end =\n location.end === undefined\n ? ''\n : `${location.start === undefined ? ':' : '–'}${location.end}`\n return `${location.file}${start}${end}`\n}\n\nexport function MessageContext({\n message,\n labels,\n copyId = true,\n copyOptions,\n renderLocation = locationText,\n}: MessageContextProps): ReactNode {\n const {Metadata} = useEditorDesignSystem()\n const text = {...defaults, ...labels}\n return (\n <Metadata label={text.title}>\n {message && (\n <dl>\n <div>\n <dt>{text.id}</dt>\n <dd>\n <code>{message.id}</code>\n {copyId && (\n <CopyTextButton\n {...copyOptions}\n value={message.id}\n label={text.id}\n />\n )}\n </dd>\n </div>\n {message.description && (\n <div>\n <dt>{text.description}</dt>\n <dd>{message.description}</dd>\n </div>\n )}\n {!!message.catalogs?.length && (\n <div>\n <dt>{text.catalogs}</dt>\n <dd>\n <ul>\n {message.catalogs.map((catalog, index) => (\n <li key={index}>{catalog}</li>\n ))}\n </ul>\n </dd>\n </div>\n )}\n {!!message.locations?.length && (\n <div>\n <dt>{text.locations}</dt>\n <dd>\n <ul>\n {message.locations.map((location, index) => (\n <li key={index}>{renderLocation(location)}</li>\n ))}\n </ul>\n </dd>\n </div>\n )}\n </dl>\n )}\n </Metadata>\n )\n}\n","import {useId, type ReactNode} from 'react'\nimport {useEditorDesignSystem} from '#packages/editor/design-system.js'\nexport {\n type EditorButtonProps,\n type EditorInputProps,\n type EditorTextInputProps,\n type EditorTextAreaProps,\n type EditorMessageRowProps,\n type EditorPanelProps,\n type EditorLayoutProps,\n type EditorComponents,\n type EditorCheckboxProps,\n type EditorLocalePickerLayoutProps,\n type EditorPreviewTokenProps,\n type EditorCopyStatus,\n type EditorCopyButtonProps,\n type EditorMetadataProps,\n type EditorToolComponents,\n type ResolvedEditorComponents,\n nativeEditorComponents,\n type EditorDesignSystemProviderProps,\n EditorDesignSystemProvider,\n useEditorDesignSystem,\n} from '#packages/editor/design-system.js'\nexport {\n type LocalePickerLabels,\n type LocalePickerProps,\n LocalePicker,\n} from '#packages/editor/locale-picker.js'\nexport {\n type MessagePreviewProps,\n MessagePreview,\n} from '#packages/editor/message-preview.js'\nexport {\n type CopyTextLabels,\n type CopyTextButtonProps,\n CopyTextButton,\n} from '#packages/editor/copy-text-button.js'\nexport {\n type MessageContextLabels,\n type MessageContextProps,\n MessageContext,\n} from '#packages/editor/message-context.js'\nimport type {\n EditorMessage,\n TranslationDraftState,\n} from '#packages/editor/workflow.js'\nimport type {TranslationValidationError} from '#packages/editor/validation.js'\n\nexport interface EditorLabels {\n search: string\n messages: string\n source: string\n noMessages: string\n noSelection: string\n loading: string\n copySource: string\n reset: string\n save: string\n saving: string\n saved: string\n unsaved: string\n unchanged: string\n validation: Record<TranslationValidationError, string>\n}\nconst defaultLabels: EditorLabels = {\n search: 'Search messages',\n messages: 'Messages',\n source: 'Source message',\n noMessages: 'No matching messages',\n noSelection: 'Select a message',\n loading: 'Loading messages…',\n copySource: 'Copy source',\n reset: 'Reset',\n save: 'Save translation',\n saving: 'Saving…',\n saved: 'Translation saved.',\n unsaved: 'Unsaved changes',\n unchanged: 'No unsaved changes',\n validation: {\n empty: 'Enter a translation before saving.',\n 'invalid-source': 'The source contains invalid ICU syntax.',\n 'invalid-translation': 'The translation contains invalid ICU syntax.',\n structure:\n 'Preserve ICU arguments, tags, formatting styles, and selector branches.',\n },\n}\nexport type EditorLabelOverrides = Partial<Omit<EditorLabels, 'validation'>> & {\n validation?: Partial<EditorLabels['validation']>\n}\nfunction resolveLabels(labels?: EditorLabelOverrides): EditorLabels {\n return {\n ...defaultLabels,\n ...labels,\n validation: {...defaultLabels.validation, ...labels?.validation},\n }\n}\ninterface ViewOptions {\n labels?: EditorLabelOverrides\n}\nexport type EditorViewMessage = Pick<\n EditorMessage,\n 'id' | 'defaultMessage' | 'description'\n>\nexport interface EditorSearch {\n value: string\n onValueChange: (value: string) => void\n}\nexport interface EditorMessageRenderState {\n selected: boolean\n}\nexport interface MessageListProps<\n Message extends EditorViewMessage = EditorViewMessage,\n> extends ViewOptions {\n messages: readonly Message[]\n selectedId?: string\n onSelect: (id: string) => void\n search?: EditorSearch\n loading?: boolean\n pagination?: ReactNode\n /** Summary or controls between search and the loaded rows. */\n listSummary?: ReactNode\n /** Noninteractive content inside the design-system selection control. */\n renderMessage?: (\n message: Message,\n state: EditorMessageRenderState\n ) => ReactNode\n /** Interactive actions rendered beside, never inside, the selection control. */\n renderMessageActions?: (\n message: Message,\n state: EditorMessageRenderState\n ) => ReactNode\n}\n/** Controlled list: it never filters, paginates, fetches, or changes selection itself. */\nexport function MessageList<\n Message extends EditorViewMessage = EditorViewMessage,\n>({\n messages,\n selectedId,\n onSelect,\n search,\n loading = false,\n pagination,\n listSummary,\n renderMessage,\n renderMessageActions,\n labels,\n}: MessageListProps<Message>): ReactNode {\n const {TextInput, MessageRow} = useEditorDesignSystem()\n const text = resolveLabels(labels)\n const searchId = useId()\n return (\n <nav aria-label={text.messages} aria-busy={loading}>\n {search && (\n <div>\n <label htmlFor={searchId}>{text.search}</label>\n <TextInput\n id={searchId}\n type=\"search\"\n value={search.value}\n onValueChange={search.onValueChange}\n />\n </div>\n )}\n {listSummary}\n {loading && (\n <p>\n <output>{text.loading}</output>\n </p>\n )}\n <ul>\n {messages.map(message => {\n const state = {selected: message.id === selectedId}\n return (\n <li key={message.id}>\n <MessageRow\n selected={state.selected}\n onSelect={() => onSelect(message.id)}\n >\n {renderMessage ? (\n renderMessage(message, state)\n ) : (\n <>\n <span>{message.defaultMessage}</span>{' '}\n <code>{message.id}</code>\n </>\n )}\n </MessageRow>\n {renderMessageActions?.(message, state)}\n </li>\n )\n })}\n </ul>\n {!loading && messages.length === 0 && (\n <p>\n <output>{text.noMessages}</output>\n </p>\n )}\n {pagination}\n </nav>\n )\n}\nexport interface SourceMessageProps extends ViewOptions {\n message: EditorViewMessage\n preview?: ReactNode\n context?: ReactNode\n}\nexport function SourceMessage({\n message,\n preview,\n context,\n labels,\n}: SourceMessageProps): ReactNode {\n const {Panel} = useEditorDesignSystem()\n const text = resolveLabels(labels)\n return (\n <Panel kind=\"source\" label={text.source}>\n <h2>{text.source}</h2>\n <code>{message.id}</code>\n {preview ?? <pre>{message.defaultMessage}</pre>}\n {message.description && <p>{message.description}</p>}\n {context}\n </Panel>\n )\n}\nexport type TranslationFieldDraft = Pick<\n TranslationDraftState,\n | 'value'\n | 'validationError'\n | 'changed'\n | 'isSaving'\n | 'saveError'\n | 'saved'\n | 'setTranslation'\n | 'reset'\n>\nexport interface TranslationFieldProps extends ViewOptions {\n locale: string\n /** Human-readable label; defaults to the locale identifier. */\n label?: string\n source: string\n draft: TranslationFieldDraft\n /** The caller owns confirmation, context, persistence, and receipt presentation. */\n onSave?: () => void\n /** Overrides the default copy/reset/save actions, including with null. */\n actions?: ReactNode\n preview?: ReactNode\n}\nexport function TranslationField({\n locale,\n label = locale,\n source,\n draft,\n onSave,\n actions,\n preview,\n labels,\n}: TranslationFieldProps): ReactNode {\n const {Panel, TextArea, Button} = useEditorDesignSystem()\n const text = resolveLabels(labels)\n const id = useId()\n const errorId = `${id}-error`\n const statusId = `${id}-status`\n const validation = draft.validationError\n ? text.validation[draft.validationError]\n : null\n const error = validation ?? draft.saveError?.message\n return (\n <Panel kind=\"translation\" label={label}>\n <label htmlFor={id}>{label}</label>\n <TextArea\n id={id}\n value={draft.value}\n onValueChange={draft.setTranslation}\n aria-invalid={!!validation}\n aria-describedby={error ? `${errorId} ${statusId}` : statusId}\n />\n {error && (\n <p id={errorId} role=\"alert\">\n {error}\n </p>\n )}\n <p>\n <output id={statusId}>\n {draft.isSaving\n ? text.saving\n : draft.saved\n ? text.saved\n : draft.changed\n ? text.unsaved\n : text.unchanged}\n </output>\n </p>\n {preview}\n {actions !== undefined ? (\n actions\n ) : (\n <div>\n <Button\n variant=\"secondary\"\n disabled={draft.isSaving}\n onPress={() => draft.setTranslation(source)}\n >\n {text.copySource}\n </Button>\n <Button\n variant=\"secondary\"\n disabled={!draft.changed || draft.isSaving}\n onPress={draft.reset}\n >\n {text.reset}\n </Button>\n {onSave && (\n <Button\n variant=\"primary\"\n disabled={\n !draft.changed || !!draft.validationError || draft.isSaving\n }\n onPress={onSave}\n >\n {draft.isSaving ? text.saving : text.save}\n </Button>\n )}\n </div>\n )}\n </Panel>\n )\n}\nexport type EditorTranslation = Omit<TranslationFieldProps, 'source'>\nexport interface TranslationEditorViewProps<\n Message extends EditorViewMessage = EditorViewMessage,\n> extends MessageListProps<Message> {\n /** May be outside the loaded list, e.g. during a server-side page change. */\n selectedMessage?: EditorViewMessage\n translations: readonly EditorTranslation[]\n filters?: ReactNode\n context?: ReactNode\n sourcePreview?: ReactNode\n notice?: ReactNode\n sidebar?: ReactNode\n /** Replaces the no-selection status; null suppresses it. */\n emptyState?: ReactNode\n /** Wrap the generated locale fields without recreating their wiring. */\n renderTranslations?: (fields: ReactNode) => ReactNode\n /** Wrap the entire detail region, including notices and the empty state. */\n renderContent?: (content: ReactNode) => ReactNode\n}\n/** A stateless composition over caller-owned navigation and locale drafts. */\nexport function TranslationEditorView<\n Message extends EditorViewMessage = EditorViewMessage,\n>({\n selectedMessage,\n selectedId = selectedMessage?.id,\n translations,\n filters,\n context,\n sourcePreview,\n notice,\n sidebar,\n emptyState,\n renderTranslations,\n renderContent,\n labels,\n ...list\n}: TranslationEditorViewProps<Message>): ReactNode {\n const {Layout} = useEditorDesignSystem()\n const text = resolveLabels(labels)\n const fields = selectedMessage\n ? translations.map(translation => (\n <TranslationField\n {...translation}\n key={`${selectedMessage.id}:${translation.locale}`}\n source={selectedMessage.defaultMessage}\n labels={{\n ...labels,\n ...translation.labels,\n validation: {\n ...labels?.validation,\n ...translation.labels?.validation,\n },\n }}\n />\n ))\n : null\n const content = (\n <>\n {notice}\n {selectedMessage ? (\n <>\n <SourceMessage\n message={selectedMessage}\n preview={sourcePreview}\n context={context}\n labels={labels}\n />\n {renderTranslations ? renderTranslations(fields) : fields}\n </>\n ) : emptyState !== undefined ? (\n emptyState\n ) : (\n <p>\n <output>{text.noSelection}</output>\n </p>\n )}\n </>\n )\n return (\n <Layout\n toolbar={filters}\n navigation={\n <MessageList {...list} selectedId={selectedId} labels={labels} />\n }\n content={renderContent ? renderContent(content) : content}\n sidebar={sidebar}\n />\n )\n}\n"],"mappings":"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"}