@singleton-sd/post-kit-editor 0.5.0 → 0.7.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 (42) hide show
  1. package/README.md +81 -10
  2. package/dist/EditorErrorBoundary.d.ts +15 -0
  3. package/dist/EditorErrorBoundary.js +32 -0
  4. package/dist/EditorErrorBoundary.js.map +1 -0
  5. package/dist/canvas/EmailBuilderCanvas.js +2 -2
  6. package/dist/canvas/EmailBuilderCanvas.js.map +1 -1
  7. package/dist/editor-shell-states.d.ts +8 -0
  8. package/dist/editor-shell-states.js +15 -0
  9. package/dist/editor-shell-states.js.map +1 -0
  10. package/dist/email-template-editor.d.ts +6 -2
  11. package/dist/email-template-editor.js +78 -35
  12. package/dist/email-template-editor.js.map +1 -1
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +4 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/metadata/MetadataPanel.d.ts +3 -1
  17. package/dist/metadata/MetadataPanel.js +15 -3
  18. package/dist/metadata/MetadataPanel.js.map +1 -1
  19. package/dist/preview/PreviewDataEditor.d.ts +3 -1
  20. package/dist/preview/PreviewDataEditor.js +9 -2
  21. package/dist/preview/PreviewDataEditor.js.map +1 -1
  22. package/dist/preview/PreviewPane.d.ts +3 -1
  23. package/dist/preview/PreviewPane.js +5 -1
  24. package/dist/preview/PreviewPane.js.map +1 -1
  25. package/dist/save-send/SaveSendBar.d.ts +3 -1
  26. package/dist/save-send/SaveSendBar.js +8 -3
  27. package/dist/save-send/SaveSendBar.js.map +1 -1
  28. package/dist/save-send/actions.d.ts +70 -0
  29. package/dist/save-send/actions.js +75 -0
  30. package/dist/save-send/actions.js.map +1 -0
  31. package/dist/validation/ValidationSummary.d.ts +5 -0
  32. package/dist/validation/ValidationSummary.js +29 -0
  33. package/dist/validation/ValidationSummary.js.map +1 -0
  34. package/dist/validation/extract-placeholders.d.ts +2 -0
  35. package/dist/validation/extract-placeholders.js +49 -0
  36. package/dist/validation/extract-placeholders.js.map +1 -0
  37. package/dist/validation/validate.d.ts +17 -0
  38. package/dist/validation/validate.js +154 -0
  39. package/dist/validation/validate.js.map +1 -0
  40. package/dist/variables/VariableCatalogue.js +1 -1
  41. package/dist/variables/VariableCatalogue.js.map +1 -1
  42. package/package.json +4 -2
package/README.md CHANGED
@@ -25,14 +25,25 @@ import {
25
25
  EmailTemplateEditor,
26
26
  type TemplateSourceFiles,
27
27
  type SerializedTemplateSource,
28
+ type ValidationIssue,
28
29
  } from '@singleton-sd/post-kit-editor';
29
30
 
30
- export function TemplateAdminPage({ template }: { template: TemplateSourceFiles }) {
31
+ export function TemplateAdminPage({
32
+ template,
33
+ loading,
34
+ loadError,
35
+ }: {
36
+ template: TemplateSourceFiles;
37
+ loading?: boolean;
38
+ loadError?: string;
39
+ }) {
31
40
  return (
32
41
  <EmailTemplateEditor
33
42
  template={template}
34
43
  availableVariables={[{ name: 'name', description: 'Recipient display name' }]}
35
- onSave={async (serialized, files) => {
44
+ loading={loading}
45
+ loadError={loadError}
46
+ onSave={async (serialized: SerializedTemplateSource, files: TemplateSourceFiles) => {
36
47
  // Commit serialized.templateJson / metadataJson / previewJson to the
37
48
  // consumer repository (e.g. via the app's own server endpoint).
38
49
  const res = await fetch('/api/templates', {
@@ -47,10 +58,9 @@ export function TemplateAdminPage({ template }: { template: TemplateSourceFiles
47
58
  }}
48
59
  onSendTest={async (serialized, _files, recipient) => {
49
60
  // Browser → your trusted server only. The server uses
50
- // @singleton-sd/post-kit-client with POSTKIT_API_KEY from Azure Key
51
- // Vault `ssd-global-kv-prod-ae` (production). Local `.env` is for
52
- // development only — never embed a long-lived PostKit API key in
53
- // browser code.
61
+ // @singleton-sd/post-kit-client with secrets from Azure Key Vault
62
+ // (production) or local `.env` (development). Never embed a
63
+ // long-lived PostKit API key in browser code.
54
64
  const res = await fetch('/api/templates/send-test', {
55
65
  method: 'POST',
56
66
  headers: { 'Content-Type': 'application/json' },
@@ -63,6 +73,9 @@ export function TemplateAdminPage({ template }: { template: TemplateSourceFiles
63
73
  onDirtyChange={(dirty) => {
64
74
  // Optional: guard in-app navigation while dirty.
65
75
  }}
76
+ onValidationChange={(issues: ValidationIssue[]) => {
77
+ // Optional: mirror validation in the host chrome.
78
+ }}
66
79
  onPreviewRendered={(html) => {
67
80
  console.log('preview bytes', html.length);
68
81
  }}
@@ -72,6 +85,39 @@ export function TemplateAdminPage({ template }: { template: TemplateSourceFiles
72
85
  }
73
86
  ```
74
87
 
88
+ A copy-pasteable single-file integration (plus synthetic sample JSON) lives in
89
+ [`examples/minimal/`](./examples/minimal/).
90
+
91
+ ## Props
92
+
93
+ | Prop | Type | Required | Description |
94
+ | --- | --- | --- | --- |
95
+ | `template` | `TemplateSourceFiles` | yes | Seeded working triple (`templateJson`, `metadata`, `previewData`). |
96
+ | `onSave` | `(serialized, files) => SaveResult \| void \| Promise<…>` | yes | Persist working files. Receives serialized Git strings **and** structured files. |
97
+ | `onSendTest` | `(serialized, files, recipient) => SendTestResult \| void \| Promise<…>` | no | When set, shows Send-test chrome. Must route through the consumer’s trusted server. |
98
+ | `availableVariables` | `TemplateVariable[]` | no | Catalogue entries offered to the editing user. |
99
+ | `onDirtyChange` | `(dirty: boolean) => void` | no | Fires when working state diverges from the seeded `template` (resets after successful save). |
100
+ | `onValidationChange` | `(issues: ValidationIssue[]) => void` | no | Current validation issues whenever they change. |
101
+ | `onPreviewRendered` | `(html: string) => void` | no | Successful preview HTML (e.g. open-in-new-tab without recompiling). |
102
+ | `loading` | `boolean` | no | Non-interactive loading shell while the host fetches files. |
103
+ | `loadError` | `string` | no | Non-interactive error shell with the host’s message. |
104
+ | `className` | `string` | no | Extra class on the root element. |
105
+
106
+ Also exported: `loadTemplateSource`, `serializeTemplateSource`,
107
+ `validateTemplate`, `hasValidationErrors`, `EDITOR_CLASS_PREFIX`, and related
108
+ types.
109
+
110
+ ## What this package does not do
111
+
112
+ - **No persistence** — it never writes to disk, Git, or object storage. `onSave`
113
+ is the only way contents leave the editor.
114
+ - **No sending** — it never calls PostKit or any mail transport. `onSendTest`
115
+ (when provided) is a consumer callback only.
116
+ - **No credentials** — there are no API-key / token props. Secrets stay on the
117
+ consumer’s trusted server (Key Vault / env), never in browser bundles.
118
+ - **No publish pipeline** — compilation and content hashing belong to
119
+ `@singleton-sd/post-kit-compiler` in CI or server tooling.
120
+
75
121
  ## Persistence is consumer-supplied
76
122
 
77
123
  Save serializes the working state with `serializeTemplateSource()` and passes
@@ -82,12 +128,29 @@ failure messages (no unhandled rejections).
82
128
 
83
129
  Send-test chrome appears **only** when `onSendTest` is provided. The editor
84
130
  validates a non-empty, plausible recipient address, then invokes the callback
85
- with the same serialized payload. Test delivery must go through the consumer's
86
- trusted server (typically `@singleton-sd/post-kit-client` server-side).
131
+ with the same serialized payload plus the recipient. Test delivery must go
132
+ through the consumer's trusted server (typically `@singleton-sd/post-kit-client`
133
+ server-side).
87
134
 
88
135
  Optional `onDirtyChange` fires when working state diverges from the seeded
89
136
  `template` prop and resets after a successful save.
90
137
 
138
+ ## Validation and accessibility
139
+
140
+ `validateTemplate(files)` (also exported) checks required metadata, key charset,
141
+ undeclared / unused variables, preview coverage, and optional `render-failed`
142
+ from the preview pane (passed through so the compiler is not invoked twice).
143
+ Error-severity issues disable Save and Send-test; warnings never block.
144
+
145
+ Optional props:
146
+
147
+ - `onValidationChange` — current `ValidationIssue[]` whenever it changes
148
+ - `loading` — non-interactive loading shell while the host fetches files
149
+ - `loadError` — non-interactive error shell with the host’s message
150
+
151
+ Inline field errors use `aria-describedby`. The validation summary is an
152
+ `aria-live` region; each entry focuses the responsible control.
153
+
91
154
  ## Preview data (synthetic only)
92
155
 
93
156
  `preview.json` is edited in the preview-data panel and used to render the
@@ -127,12 +190,16 @@ against the resulting HTML string:
127
190
  ```tsx
128
191
  import { renderToStaticMarkup } from 'react-dom/server';
129
192
 
130
- const html = renderToStaticMarkup(<EmailTemplateEditor template={template} onSave={() => {}} />);
193
+ const html = renderToStaticMarkup(
194
+ <EmailTemplateEditor template={template} onSave={() => {}} />,
195
+ );
131
196
  ```
132
197
 
133
198
  Specs live next to the code as `src/**/*.spec.tsx`. Behaviour that needs
134
199
  interaction is factored into pure functions (preview rows, save/send helpers)
135
- that can be tested without a DOM.
200
+ that can be tested without a DOM. The end-to-end suite (`src/e2e.spec.tsx`)
201
+ drives load → edit → validate → save / send-test through those helpers plus SSR
202
+ markup.
136
203
 
137
204
  ## Development
138
205
 
@@ -141,3 +208,7 @@ pnpm test # type-check + run tests
141
208
  pnpm build # emit CommonJS to dist/
142
209
  pnpm lint # covered by root eslint
143
210
  ```
211
+
212
+ `examples/` is documentation-only: it is not listed in the package `files`
213
+ array and is outside `src/`, so it is neither published to npm nor emitted into
214
+ `dist/`.
@@ -0,0 +1,15 @@
1
+ import { Component, type ErrorInfo, type ReactNode } from 'react';
2
+ export interface EditorErrorBoundaryProps {
3
+ children: ReactNode;
4
+ }
5
+ interface EditorErrorBoundaryState {
6
+ error: Error | null;
7
+ }
8
+ export declare class EditorErrorBoundary extends Component<EditorErrorBoundaryProps, EditorErrorBoundaryState> {
9
+ state: EditorErrorBoundaryState;
10
+ static getDerivedStateFromError(error: Error): EditorErrorBoundaryState;
11
+ componentDidCatch(error: Error, info: ErrorInfo): void;
12
+ private handleRetry;
13
+ render(): ReactNode;
14
+ }
15
+ export {};
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EditorErrorBoundary = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const email_template_editor_1 = require("./email-template-editor");
7
+ class EditorErrorBoundary extends react_1.Component {
8
+ constructor() {
9
+ super(...arguments);
10
+ this.state = { error: null };
11
+ this.handleRetry = () => {
12
+ this.setState({ error: null });
13
+ };
14
+ }
15
+ static getDerivedStateFromError(error) {
16
+ return { error };
17
+ }
18
+ componentDidCatch(error, info) {
19
+ if (typeof process !== 'undefined' && process.env.NODE_ENV !== 'test') {
20
+ console.error('post-kit-editor render failure', error, info.componentStack);
21
+ }
22
+ }
23
+ render() {
24
+ const p = email_template_editor_1.EDITOR_CLASS_PREFIX;
25
+ if (this.state.error) {
26
+ return ((0, jsx_runtime_1.jsxs)("div", { className: `${p}fatal-error`, "data-testid": `${p}fatal-error`, role: "alert", children: [(0, jsx_runtime_1.jsx)("h2", { className: `${p}fatal-error-heading`, children: "Editor failed to render" }), (0, jsx_runtime_1.jsx)("p", { className: `${p}fatal-error-message`, children: this.state.error.message }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: `${p}fatal-error-retry`, "data-testid": `${p}fatal-error-retry`, onClick: this.handleRetry, children: "Retry" })] }));
27
+ }
28
+ return this.props.children;
29
+ }
30
+ }
31
+ exports.EditorErrorBoundary = EditorErrorBoundary;
32
+ //# sourceMappingURL=EditorErrorBoundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditorErrorBoundary.js","sourceRoot":"","sources":["../src/EditorErrorBoundary.tsx"],"names":[],"mappings":";;;;AAAA,iCAAyE;AAEzE,mEAA8D;AAc9D,MAAa,mBAAoB,SAAQ,iBAGxC;IAHD;;QAIE,UAAK,GAA6B,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAa1C,gBAAW,GAAG,GAAS,EAAE;YAC/B,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACjC,CAAC,CAAC;IAsBJ,CAAC;IAnCC,MAAM,CAAC,wBAAwB,CAAC,KAAY;QAC1C,OAAO,EAAE,KAAK,EAAE,CAAC;IACnB,CAAC;IAED,iBAAiB,CAAC,KAAY,EAAE,IAAe;QAE7C,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;YACtE,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IAMD,MAAM;QACJ,MAAM,CAAC,GAAG,2CAAmB,CAAC;QAC9B,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACrB,OAAO,CACL,iCAAK,SAAS,EAAE,GAAG,CAAC,aAAa,iBAAe,GAAG,CAAC,aAAa,EAAE,IAAI,EAAC,OAAO,aAC7E,+BAAI,SAAS,EAAE,GAAG,CAAC,qBAAqB,wCAA8B,EACtE,8BAAG,SAAS,EAAE,GAAG,CAAC,qBAAqB,YAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,GAAK,EACvE,mCACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,GAAG,CAAC,mBAAmB,iBACrB,GAAG,CAAC,mBAAmB,EACpC,OAAO,EAAE,IAAI,CAAC,WAAW,sBAGlB,IACL,CACP,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;IAC7B,CAAC;CACF;AAzCD,kDAyCC"}
@@ -8,8 +8,8 @@ const EditorBlock_1 = require("./EditorBlock");
8
8
  const email_builder_1 = require("@usewaypoint/email-builder");
9
9
  function EmailBuilderCanvas({ document, onChange, readOnly = false, }) {
10
10
  if (readOnly) {
11
- return ((0, jsx_runtime_1.jsx)("div", { className: `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, "data-testid": `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, children: (0, jsx_runtime_1.jsx)(email_builder_1.Reader, { document: document, rootBlockId: "root" }) }));
11
+ return ((0, jsx_runtime_1.jsx)("div", { id: `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, className: `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, "data-testid": `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, role: "region", tabIndex: -1, "aria-label": "Email document canvas", children: (0, jsx_runtime_1.jsx)(email_builder_1.Reader, { document: document, rootBlockId: "root" }) }));
12
12
  }
13
- return ((0, jsx_runtime_1.jsx)(editor_context_1.CanvasEditorProvider, { document: document, onChange: onChange, children: (0, jsx_runtime_1.jsx)("div", { className: `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, "data-testid": `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, children: (0, jsx_runtime_1.jsx)(EditorBlock_1.EditorBlock, { id: "root" }) }) }));
13
+ return ((0, jsx_runtime_1.jsx)(editor_context_1.CanvasEditorProvider, { document: document, onChange: onChange, children: (0, jsx_runtime_1.jsx)("div", { id: `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, className: `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, "data-testid": `${email_template_editor_1.EDITOR_CLASS_PREFIX}canvas`, role: "region", tabIndex: -1, "aria-label": "Email document canvas", children: (0, jsx_runtime_1.jsx)(EditorBlock_1.EditorBlock, { id: "root" }) }) }));
14
14
  }
15
15
  //# sourceMappingURL=EmailBuilderCanvas.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EmailBuilderCanvas.js","sourceRoot":"","sources":["../../src/canvas/EmailBuilderCanvas.tsx"],"names":[],"mappings":";;AAcA,gDAoBC;;AA/BD,oEAA+D;AAC/D,qDAAwD;AACxD,+CAA4C;AAC5C,8DAAoD;AAQpD,SAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,QAAQ,EACR,QAAQ,GAAG,KAAK,GACQ;IACxB,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,CACL,gCAAK,SAAS,EAAE,GAAG,2CAAmB,QAAQ,iBAAe,GAAG,2CAAmB,QAAQ,YACzF,uBAAC,sBAAM,IAAC,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAC,MAAM,GAAG,GAC7C,CACP,CAAC;IACJ,CAAC;IAED,OAAO,CACL,uBAAC,qCAAoB,IAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,YAC1D,gCAAK,SAAS,EAAE,GAAG,2CAAmB,QAAQ,iBAAe,GAAG,2CAAmB,QAAQ,YACzF,uBAAC,yBAAW,IAAC,EAAE,EAAC,MAAM,GAAG,GACrB,GACe,CACxB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"EmailBuilderCanvas.js","sourceRoot":"","sources":["../../src/canvas/EmailBuilderCanvas.tsx"],"names":[],"mappings":";;AAcA,gDAkCC;;AA7CD,oEAA+D;AAC/D,qDAAwD;AACxD,+CAA4C;AAC5C,8DAAoD;AAQpD,SAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,QAAQ,EACR,QAAQ,GAAG,KAAK,GACQ;IACxB,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,CACL,gCACE,EAAE,EAAE,GAAG,2CAAmB,QAAQ,EAClC,SAAS,EAAE,GAAG,2CAAmB,QAAQ,iBAC5B,GAAG,2CAAmB,QAAQ,EAC3C,IAAI,EAAC,QAAQ,EACb,QAAQ,EAAE,CAAC,CAAC,gBACD,uBAAuB,YAElC,uBAAC,sBAAM,IAAC,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAC,MAAM,GAAG,GAC7C,CACP,CAAC;IACJ,CAAC;IAED,OAAO,CACL,uBAAC,qCAAoB,IAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,YAC1D,gCACE,EAAE,EAAE,GAAG,2CAAmB,QAAQ,EAClC,SAAS,EAAE,GAAG,2CAAmB,QAAQ,iBAC5B,GAAG,2CAAmB,QAAQ,EAC3C,IAAI,EAAC,QAAQ,EACb,QAAQ,EAAE,CAAC,CAAC,gBACD,uBAAuB,YAElC,uBAAC,yBAAW,IAAC,EAAE,EAAC,MAAM,GAAG,GACrB,GACe,CACxB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ export interface EditorLoadingStateProps {
2
+ message?: string;
3
+ }
4
+ export declare function EditorLoadingState({ message, }: EditorLoadingStateProps): JSX.Element;
5
+ export interface EditorLoadErrorStateProps {
6
+ message: string;
7
+ }
8
+ export declare function EditorLoadErrorState({ message }: EditorLoadErrorStateProps): JSX.Element;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EditorLoadingState = EditorLoadingState;
4
+ exports.EditorLoadErrorState = EditorLoadErrorState;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const email_template_editor_1 = require("./email-template-editor");
7
+ function EditorLoadingState({ message = 'Loading template…', }) {
8
+ const p = email_template_editor_1.EDITOR_CLASS_PREFIX;
9
+ return ((0, jsx_runtime_1.jsx)("div", { className: `${p}loading`, "data-testid": `${p}loading`, role: "status", "aria-busy": "true", "aria-live": "polite", children: (0, jsx_runtime_1.jsx)("p", { className: `${p}loading-message`, children: message }) }));
10
+ }
11
+ function EditorLoadErrorState({ message }) {
12
+ const p = email_template_editor_1.EDITOR_CLASS_PREFIX;
13
+ return ((0, jsx_runtime_1.jsxs)("div", { className: `${p}load-error`, "data-testid": `${p}load-error`, role: "alert", children: [(0, jsx_runtime_1.jsx)("h2", { className: `${p}load-error-heading`, children: "Could not load template" }), (0, jsx_runtime_1.jsx)("p", { className: `${p}load-error-message`, children: message })] }));
14
+ }
15
+ //# sourceMappingURL=editor-shell-states.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor-shell-states.js","sourceRoot":"","sources":["../src/editor-shell-states.tsx"],"names":[],"mappings":";;AAUA,gDAeC;AAOD,oDAQC;;AAtCD,mEAA8D;AAQ9D,SAAgB,kBAAkB,CAAC,EACjC,OAAO,GAAG,mBAAmB,GACL;IACxB,MAAM,CAAC,GAAG,2CAAmB,CAAC;IAC9B,OAAO,CACL,gCACE,SAAS,EAAE,GAAG,CAAC,SAAS,iBACX,GAAG,CAAC,SAAS,EAC1B,IAAI,EAAC,QAAQ,eACH,MAAM,eACN,QAAQ,YAElB,8BAAG,SAAS,EAAE,GAAG,CAAC,iBAAiB,YAAG,OAAO,GAAK,GAC9C,CACP,CAAC;AACJ,CAAC;AAOD,SAAgB,oBAAoB,CAAC,EAAE,OAAO,EAA6B;IACzE,MAAM,CAAC,GAAG,2CAAmB,CAAC;IAC9B,OAAO,CACL,iCAAK,SAAS,EAAE,GAAG,CAAC,YAAY,iBAAe,GAAG,CAAC,YAAY,EAAE,IAAI,EAAC,OAAO,aAC3E,+BAAI,SAAS,EAAE,GAAG,CAAC,oBAAoB,wCAA8B,EACrE,8BAAG,SAAS,EAAE,GAAG,CAAC,oBAAoB,YAAG,OAAO,GAAK,IACjD,CACP,CAAC;AACJ,CAAC"}
@@ -1,5 +1,6 @@
1
- import { type SaveResult, type SendTestResult, type SerializedTemplateSource } from './save-send/types';
1
+ import type { SaveResult, SendTestResult, SerializedTemplateSource } from './save-send/types';
2
2
  import type { TemplateSourceFiles, TemplateVariable } from './types';
3
+ import { type ValidationIssue } from './validation/validate';
3
4
  export declare const EDITOR_CLASS_PREFIX = "pk-editor-";
4
5
  export interface EmailTemplateEditorProps {
5
6
  template: TemplateSourceFiles;
@@ -7,7 +8,10 @@ export interface EmailTemplateEditorProps {
7
8
  onSave: (serialized: SerializedTemplateSource, files: TemplateSourceFiles) => Promise<SaveResult | void> | SaveResult | void;
8
9
  onSendTest?: (serialized: SerializedTemplateSource, files: TemplateSourceFiles, recipient: string) => Promise<SendTestResult | void> | SendTestResult | void;
9
10
  onDirtyChange?: (dirty: boolean) => void;
11
+ onValidationChange?: (issues: ValidationIssue[]) => void;
10
12
  onPreviewRendered?: (html: string) => void;
13
+ loading?: boolean;
14
+ loadError?: string;
11
15
  className?: string;
12
16
  }
13
- export declare function EmailTemplateEditor({ template, availableVariables, onSave, onSendTest, onDirtyChange, onPreviewRendered, className, }: EmailTemplateEditorProps): JSX.Element;
17
+ export declare function EmailTemplateEditor(props: EmailTemplateEditorProps): JSX.Element;
@@ -5,24 +5,42 @@ exports.EmailTemplateEditor = EmailTemplateEditor;
5
5
  const jsx_runtime_1 = require("react/jsx-runtime");
6
6
  const react_1 = require("react");
7
7
  const EmailBuilderCanvas_1 = require("./canvas/EmailBuilderCanvas");
8
+ const EditorErrorBoundary_1 = require("./EditorErrorBoundary");
9
+ const editor_shell_states_1 = require("./editor-shell-states");
8
10
  const insertion_target_1 = require("./insertion-target");
9
11
  const MetadataPanel_1 = require("./metadata/MetadataPanel");
10
12
  const PreviewDataEditor_1 = require("./preview/PreviewDataEditor");
11
13
  const PreviewPane_1 = require("./preview/PreviewPane");
12
14
  const dirty_1 = require("./save-send/dirty");
13
15
  const SaveSendBar_1 = require("./save-send/SaveSendBar");
14
- const types_1 = require("./save-send/types");
16
+ const actions_1 = require("./save-send/actions");
17
+ const ValidationSummary_1 = require("./validation/ValidationSummary");
18
+ const validate_1 = require("./validation/validate");
15
19
  const VariableCatalogue_1 = require("./variables/VariableCatalogue");
16
20
  const working_files_1 = require("./working-files");
17
21
  exports.EDITOR_CLASS_PREFIX = 'pk-editor-';
18
- function EmailTemplateEditor({ template, availableVariables, onSave, onSendTest, onDirtyChange, onPreviewRendered, className, }) {
22
+ function EmailTemplateEditor(props) {
23
+ const rootClassName = [`${exports.EDITOR_CLASS_PREFIX}root`, props.className].filter(Boolean).join(' ');
24
+ if (props.loading) {
25
+ return ((0, jsx_runtime_1.jsx)("div", { className: rootClassName, "data-testid": `${exports.EDITOR_CLASS_PREFIX}root`, children: (0, jsx_runtime_1.jsx)(editor_shell_states_1.EditorLoadingState, {}) }));
26
+ }
27
+ if (props.loadError) {
28
+ return ((0, jsx_runtime_1.jsx)("div", { className: rootClassName, "data-testid": `${exports.EDITOR_CLASS_PREFIX}root`, children: (0, jsx_runtime_1.jsx)(editor_shell_states_1.EditorLoadErrorState, { message: props.loadError }) }));
29
+ }
30
+ return ((0, jsx_runtime_1.jsx)("div", { className: rootClassName, "data-testid": `${exports.EDITOR_CLASS_PREFIX}root`, children: (0, jsx_runtime_1.jsx)(EditorErrorBoundary_1.EditorErrorBoundary, { children: (0, jsx_runtime_1.jsx)(EmailTemplateEditorInner, { ...props }) }) }));
31
+ }
32
+ function EmailTemplateEditorInner({ template, availableVariables, onSave, onSendTest, onDirtyChange, onValidationChange, onPreviewRendered, }) {
19
33
  const [workingFiles, setWorkingFiles] = (0, react_1.useState)(template);
20
34
  const [seedFiles, setSeedFiles] = (0, react_1.useState)(template);
21
35
  const [saveFeedback, setSaveFeedback] = (0, react_1.useState)({ status: 'idle' });
22
36
  const [sendFeedback, setSendFeedback] = (0, react_1.useState)({ status: 'idle' });
37
+ const [previewResult, setPreviewResult] = (0, react_1.useState)(null);
23
38
  const onDirtyChangeRef = (0, react_1.useRef)(onDirtyChange);
24
39
  onDirtyChangeRef.current = onDirtyChange;
40
+ const onValidationChangeRef = (0, react_1.useRef)(onValidationChange);
41
+ onValidationChangeRef.current = onValidationChange;
25
42
  const lastDirtyRef = (0, react_1.useRef)(null);
43
+ const lastIssuesKeyRef = (0, react_1.useRef)(null);
26
44
  const templateGenerationRef = (0, react_1.useRef)(0);
27
45
  const seedFilesRef = (0, react_1.useRef)(seedFiles);
28
46
  seedFilesRef.current = seedFiles;
@@ -33,6 +51,7 @@ function EmailTemplateEditor({ template, availableVariables, onSave, onSendTest,
33
51
  templateGenerationRef.current += 1;
34
52
  setSaveFeedback({ status: 'idle' });
35
53
  setSendFeedback({ status: 'idle' });
54
+ setPreviewResult(null);
36
55
  setWorkingFiles(template);
37
56
  setSeedFiles(template);
38
57
  }, [template]);
@@ -44,6 +63,24 @@ function EmailTemplateEditor({ template, availableVariables, onSave, onSendTest,
44
63
  lastDirtyRef.current = dirty;
45
64
  onDirtyChangeRef.current?.(dirty);
46
65
  }, [seedFiles, workingFiles]);
66
+ const issues = (0, validate_1.validateTemplate)(workingFiles, {
67
+ renderError: previewResult && !previewResult.ok ? previewResult.error : null,
68
+ });
69
+ (0, react_1.useEffect)(() => {
70
+ const key = JSON.stringify(issues);
71
+ if (lastIssuesKeyRef.current === key) {
72
+ return;
73
+ }
74
+ lastIssuesKeyRef.current = key;
75
+ onValidationChangeRef.current?.(issues);
76
+ }, [issues]);
77
+ const previewPending = previewResult === null;
78
+ const validationBlocked = (0, validate_1.hasValidationErrors)(issues) || previewPending;
79
+ const validationBlockedReason = previewPending
80
+ ? 'Wait for the preview to finish before saving or sending a test.'
81
+ : 'Fix validation errors before saving or sending a test.';
82
+ const metadataIssues = issues.filter((i) => i.field === 'metadata' || i.field === 'subject');
83
+ const previewDataIssues = issues.filter((i) => i.field === 'previewData');
47
84
  const handleDocumentChange = (0, react_1.useCallback)((document) => {
48
85
  setWorkingFiles((current) => (0, working_files_1.withDocument)(current, document));
49
86
  }, []);
@@ -58,73 +95,79 @@ function EmailTemplateEditor({ template, availableVariables, onSave, onSendTest,
58
95
  }, []);
59
96
  const busy = saveFeedback.status === 'pending' || sendFeedback.status === 'pending';
60
97
  const handleSave = (0, react_1.useCallback)(() => {
61
- if (busy) {
98
+ const started = (0, actions_1.startSaveAction)({
99
+ busy,
100
+ validationBlocked,
101
+ files: workingFiles,
102
+ });
103
+ if (started.status === 'noop') {
62
104
  return;
63
105
  }
64
- let payload;
65
- try {
66
- payload = (0, types_1.buildSavePayload)(workingFiles);
67
- }
68
- catch (error) {
69
- setSaveFeedback({
70
- status: 'failure',
71
- message: error instanceof Error ? error.message : String(error),
72
- });
106
+ if (started.status === 'serialize-error') {
107
+ setSaveFeedback({ status: 'failure', message: started.message });
73
108
  return;
74
109
  }
75
110
  const generation = templateGenerationRef.current;
76
111
  setSaveFeedback({ status: 'pending' });
77
112
  void (async () => {
78
- const result = await (0, types_1.invokeConsumerAction)(() => onSave(payload.serialized, payload.files));
79
- if (generation !== templateGenerationRef.current) {
113
+ const result = await (0, actions_1.finishSaveAction)({
114
+ payload: started.payload,
115
+ isGenerationCurrent: () => generation === templateGenerationRef.current,
116
+ onSave,
117
+ });
118
+ if (result.status === 'stale') {
80
119
  return;
81
120
  }
82
- if (result.ok) {
83
- setSeedFiles(payload.files);
121
+ if (result.status === 'success') {
122
+ setSeedFiles(result.files);
84
123
  setSaveFeedback({ status: 'success', message: result.message });
85
124
  }
86
125
  else {
87
126
  setSaveFeedback({
88
127
  status: 'failure',
89
- message: result.message ?? 'Save failed.',
128
+ message: result.message,
90
129
  });
91
130
  }
92
131
  })();
93
- }, [busy, onSave, workingFiles]);
132
+ }, [busy, onSave, validationBlocked, workingFiles]);
94
133
  const handleSendTest = (0, react_1.useCallback)((recipient) => {
95
- if (busy || !onSendTest) {
134
+ const started = (0, actions_1.startSendTestAction)({
135
+ busy,
136
+ validationBlocked,
137
+ sendTestEnabled: typeof onSendTest === 'function',
138
+ files: workingFiles,
139
+ recipient,
140
+ });
141
+ if (started.status === 'noop') {
96
142
  return;
97
143
  }
98
- let payload;
99
- try {
100
- payload = (0, types_1.buildSavePayload)(workingFiles);
101
- }
102
- catch (error) {
103
- setSendFeedback({
104
- status: 'failure',
105
- message: error instanceof Error ? error.message : String(error),
106
- });
144
+ if (started.status === 'recipient-error' || started.status === 'serialize-error') {
145
+ setSendFeedback({ status: 'failure', message: started.message });
107
146
  return;
108
147
  }
109
148
  const generation = templateGenerationRef.current;
110
149
  setSendFeedback({ status: 'pending' });
111
150
  void (async () => {
112
- const result = await (0, types_1.invokeConsumerAction)(() => onSendTest(payload.serialized, payload.files, recipient));
113
- if (generation !== templateGenerationRef.current) {
151
+ const result = await (0, actions_1.finishSendTestAction)({
152
+ payload: started.payload,
153
+ recipient: started.recipient,
154
+ isGenerationCurrent: () => generation === templateGenerationRef.current,
155
+ onSendTest: onSendTest,
156
+ });
157
+ if (result.status === 'stale') {
114
158
  return;
115
159
  }
116
- if (result.ok) {
160
+ if (result.status === 'success') {
117
161
  setSendFeedback({ status: 'success', message: result.message });
118
162
  }
119
163
  else {
120
164
  setSendFeedback({
121
165
  status: 'failure',
122
- message: result.message ?? 'Send test failed.',
166
+ message: result.message,
123
167
  });
124
168
  }
125
169
  })();
126
- }, [busy, onSendTest, workingFiles]);
127
- const rootClassName = [`${exports.EDITOR_CLASS_PREFIX}root`, className].filter(Boolean).join(' ');
128
- return ((0, jsx_runtime_1.jsx)("div", { className: rootClassName, "data-testid": `${exports.EDITOR_CLASS_PREFIX}root`, children: (0, jsx_runtime_1.jsx)(insertion_target_1.InsertionTargetProvider, { children: (0, jsx_runtime_1.jsxs)("div", { className: `${exports.EDITOR_CLASS_PREFIX}layout`, "data-testid": `${exports.EDITOR_CLASS_PREFIX}layout`, children: [(0, jsx_runtime_1.jsxs)("aside", { className: `${exports.EDITOR_CLASS_PREFIX}sidebar`, children: [(0, jsx_runtime_1.jsx)(SaveSendBar_1.SaveSendBar, { saveFeedback: saveFeedback, sendFeedback: sendFeedback, busy: busy, showSendTest: typeof onSendTest === 'function', onSave: handleSave, onSendTest: handleSendTest }), (0, jsx_runtime_1.jsx)(MetadataPanel_1.MetadataPanel, { metadata: workingFiles.metadata, previewData: workingFiles.previewData, onChange: handleMetadataChange }), (0, jsx_runtime_1.jsx)(VariableCatalogue_1.VariableCatalogue, { availableVariables: availableVariables, metadataVariables: workingFiles.metadata.variables, onMetadataVariablesChange: handleMetadataVariablesChange }), (0, jsx_runtime_1.jsx)(PreviewDataEditor_1.PreviewDataEditor, { declaredVariables: workingFiles.metadata.variables, previewData: workingFiles.previewData, onChange: handlePreviewDataChange })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${exports.EDITOR_CLASS_PREFIX}main`, children: [(0, jsx_runtime_1.jsx)(EmailBuilderCanvas_1.EmailBuilderCanvas, { document: workingFiles.templateJson, onChange: handleDocumentChange }), (0, jsx_runtime_1.jsx)(PreviewPane_1.PreviewPane, { files: workingFiles, onPreviewRendered: onPreviewRendered })] })] }) }) }));
170
+ }, [busy, onSendTest, validationBlocked, workingFiles]);
171
+ return ((0, jsx_runtime_1.jsx)(insertion_target_1.InsertionTargetProvider, { children: (0, jsx_runtime_1.jsxs)("div", { className: `${exports.EDITOR_CLASS_PREFIX}layout`, "data-testid": `${exports.EDITOR_CLASS_PREFIX}layout`, children: [(0, jsx_runtime_1.jsxs)("aside", { className: `${exports.EDITOR_CLASS_PREFIX}sidebar`, "aria-label": "Template settings", children: [(0, jsx_runtime_1.jsx)(SaveSendBar_1.SaveSendBar, { saveFeedback: saveFeedback, sendFeedback: sendFeedback, busy: busy, validationBlocked: validationBlocked, validationBlockedReason: validationBlockedReason, showSendTest: typeof onSendTest === 'function', onSave: handleSave, onSendTest: handleSendTest }), (0, jsx_runtime_1.jsx)(ValidationSummary_1.ValidationSummary, { issues: issues }), (0, jsx_runtime_1.jsx)(MetadataPanel_1.MetadataPanel, { metadata: workingFiles.metadata, previewData: workingFiles.previewData, onChange: handleMetadataChange, issues: metadataIssues }), (0, jsx_runtime_1.jsx)(VariableCatalogue_1.VariableCatalogue, { availableVariables: availableVariables, metadataVariables: workingFiles.metadata.variables, onMetadataVariablesChange: handleMetadataVariablesChange }), (0, jsx_runtime_1.jsx)(PreviewDataEditor_1.PreviewDataEditor, { declaredVariables: workingFiles.metadata.variables, previewData: workingFiles.previewData, onChange: handlePreviewDataChange, issues: previewDataIssues })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${exports.EDITOR_CLASS_PREFIX}main`, "aria-label": "Template canvas and preview", children: [(0, jsx_runtime_1.jsx)(EmailBuilderCanvas_1.EmailBuilderCanvas, { document: workingFiles.templateJson, onChange: handleDocumentChange }), (0, jsx_runtime_1.jsx)(PreviewPane_1.PreviewPane, { files: workingFiles, onPreviewRendered: onPreviewRendered, onPreviewResultChange: setPreviewResult })] })] }) }));
129
172
  }
130
173
  //# sourceMappingURL=email-template-editor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"email-template-editor.js","sourceRoot":"","sources":["../src/email-template-editor.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"email-template-editor.js","sourceRoot":"","sources":["../src/email-template-editor.tsx"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -3,3 +3,4 @@ export { EDITOR_CLASS_PREFIX, EmailTemplateEditor, type EmailTemplateEditorProps
3
3
  export { EmailBuilderCanvas, type EmailBuilderCanvasProps } from './canvas/EmailBuilderCanvas';
4
4
  export { loadTemplateSource, serializeTemplateSource, TemplateSourceError } from './serialization';
5
5
  export type { SerializedTemplateSource, SaveResult, SendTestResult } from './save-send/types';
6
+ export { validateTemplate, hasValidationErrors, type ValidationIssue, type ValidationSeverity, type ValidateTemplateOptions, } from './validation/validate';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TemplateSourceError = exports.serializeTemplateSource = exports.loadTemplateSource = exports.EmailBuilderCanvas = exports.EmailTemplateEditor = exports.EDITOR_CLASS_PREFIX = void 0;
3
+ exports.hasValidationErrors = exports.validateTemplate = exports.TemplateSourceError = exports.serializeTemplateSource = exports.loadTemplateSource = exports.EmailBuilderCanvas = exports.EmailTemplateEditor = exports.EDITOR_CLASS_PREFIX = void 0;
4
4
  var email_template_editor_1 = require("./email-template-editor");
5
5
  Object.defineProperty(exports, "EDITOR_CLASS_PREFIX", { enumerable: true, get: function () { return email_template_editor_1.EDITOR_CLASS_PREFIX; } });
6
6
  Object.defineProperty(exports, "EmailTemplateEditor", { enumerable: true, get: function () { return email_template_editor_1.EmailTemplateEditor; } });
@@ -10,4 +10,7 @@ var serialization_1 = require("./serialization");
10
10
  Object.defineProperty(exports, "loadTemplateSource", { enumerable: true, get: function () { return serialization_1.loadTemplateSource; } });
11
11
  Object.defineProperty(exports, "serializeTemplateSource", { enumerable: true, get: function () { return serialization_1.serializeTemplateSource; } });
12
12
  Object.defineProperty(exports, "TemplateSourceError", { enumerable: true, get: function () { return serialization_1.TemplateSourceError; } });
13
+ var validate_1 = require("./validation/validate");
14
+ Object.defineProperty(exports, "validateTemplate", { enumerable: true, get: function () { return validate_1.validateTemplate; } });
15
+ Object.defineProperty(exports, "hasValidationErrors", { enumerable: true, get: function () { return validate_1.hasValidationErrors; } });
13
16
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAKA,iEAIiC;AAH/B,4HAAA,mBAAmB,OAAA;AACnB,4HAAA,mBAAmB,OAAA;AAGrB,kEAA+F;AAAtF,wHAAA,kBAAkB,OAAA;AAC3B,iDAAmG;AAA1F,mHAAA,kBAAkB,OAAA;AAAE,wHAAA,uBAAuB,OAAA;AAAE,oHAAA,mBAAmB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAKA,iEAIiC;AAH/B,4HAAA,mBAAmB,OAAA;AACnB,4HAAA,mBAAmB,OAAA;AAGrB,kEAA+F;AAAtF,wHAAA,kBAAkB,OAAA;AAC3B,iDAAmG;AAA1F,mHAAA,kBAAkB,OAAA;AAAE,wHAAA,uBAAuB,OAAA;AAAE,oHAAA,mBAAmB,OAAA;AAEzE,kDAM+B;AAL7B,4GAAA,gBAAgB,OAAA;AAChB,+GAAA,mBAAmB,OAAA"}
@@ -1,7 +1,9 @@
1
1
  import type { TemplatePreviewData, TemplateSourceMetadata } from '@singleton-sd/post-kit-types';
2
+ import { type ValidationIssue } from '../validation/validate';
2
3
  export interface MetadataPanelProps {
3
4
  metadata: TemplateSourceMetadata;
4
5
  previewData: TemplatePreviewData;
5
6
  onChange: (metadata: TemplateSourceMetadata) => void;
7
+ issues?: readonly ValidationIssue[];
6
8
  }
7
- export declare function MetadataPanel({ metadata, previewData, onChange, }: MetadataPanelProps): JSX.Element;
9
+ export declare function MetadataPanel({ metadata, previewData, onChange, issues, }: MetadataPanelProps): JSX.Element;
@@ -5,12 +5,24 @@ const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const email_template_editor_1 = require("../email-template-editor");
7
7
  const insertion_target_1 = require("../insertion-target");
8
+ const validate_1 = require("../validation/validate");
8
9
  const insert_at_cursor_1 = require("./insert-at-cursor");
9
10
  const subject_preview_1 = require("./subject-preview");
10
11
  const template_key_1 = require("./template-key");
11
- function MetadataPanel({ metadata, previewData, onChange, }) {
12
+ function MetadataPanel({ metadata, previewData, onChange, issues = [], }) {
12
13
  const [keyError, setKeyError] = (0, react_1.useState)(null);
13
14
  const subjectRef = (0, react_1.useRef)(null);
15
+ const keyIssues = issues.filter((i) => i.field === 'metadata' && (i.code === 'missing-key' || i.code === 'invalid-key'));
16
+ const nameIssues = issues.filter((i) => i.field === 'metadata' && i.code === 'missing-name');
17
+ const subjectIssues = issues.filter((i) => i.field === 'subject');
18
+ const keyDescribedBy = [
19
+ keyError ? `${email_template_editor_1.EDITOR_CLASS_PREFIX}meta-key-error` : null,
20
+ ...keyIssues.map((i) => (0, validate_1.inlineIssueId)(i.code)),
21
+ ]
22
+ .filter(Boolean)
23
+ .join(' ');
24
+ const nameDescribedBy = nameIssues.map((i) => (0, validate_1.inlineIssueId)(i.code)).join(' ');
25
+ const subjectDescribedBy = subjectIssues.map((i) => (0, validate_1.inlineIssueId)(i.code, i.variable)).join(' ');
14
26
  const patch = (0, react_1.useCallback)((partial) => {
15
27
  onChange({ ...metadata, ...partial });
16
28
  }, [metadata, onChange]);
@@ -43,7 +55,7 @@ function MetadataPanel({ metadata, previewData, onChange, }) {
43
55
  const subjectFocus = (0, insertion_target_1.useRegisterInsertionTarget)('subject field', insertIntoSubject);
44
56
  const subjectPreview = (0, subject_preview_1.previewSubject)(metadata.subject, previewData);
45
57
  const p = email_template_editor_1.EDITOR_CLASS_PREFIX;
46
- return ((0, jsx_runtime_1.jsxs)("section", { className: `${p}metadata`, "data-testid": `${p}metadata`, children: [(0, jsx_runtime_1.jsx)("h2", { className: `${p}metadata-heading`, children: "Template metadata" }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("label", { className: `${p}metadata-label`, htmlFor: `${p}meta-key`, children: "Key" }), (0, jsx_runtime_1.jsx)("input", { id: `${p}meta-key`, className: `${p}metadata-input`, type: "text", value: metadata.key, onChange: handleKeyChange, autoComplete: "off", spellCheck: false, "data-testid": `${p}meta-key` }), keyError ? ((0, jsx_runtime_1.jsx)("p", { className: `${p}metadata-error`, "data-testid": `${p}meta-key-error`, role: "status", children: keyError })) : null] }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("label", { className: `${p}metadata-label`, htmlFor: `${p}meta-name`, children: "Name" }), (0, jsx_runtime_1.jsx)("input", { id: `${p}meta-name`, className: `${p}metadata-input`, type: "text", value: metadata.name, onChange: (event) => patch({ name: event.target.value }), "data-testid": `${p}meta-name` })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("label", { className: `${p}metadata-label`, htmlFor: `${p}meta-description`, children: "Description" }), (0, jsx_runtime_1.jsx)("textarea", { id: `${p}meta-description`, className: `${p}metadata-textarea`, value: metadata.description ?? '', onChange: (event) => {
58
+ return ((0, jsx_runtime_1.jsxs)("section", { className: `${p}metadata`, "data-testid": `${p}metadata`, children: [(0, jsx_runtime_1.jsx)("h2", { className: `${p}metadata-heading`, children: "Template metadata" }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("label", { className: `${p}metadata-label`, htmlFor: `${p}meta-key`, children: "Key" }), (0, jsx_runtime_1.jsx)("input", { id: `${p}meta-key`, className: `${p}metadata-input`, type: "text", value: metadata.key, onChange: handleKeyChange, autoComplete: "off", spellCheck: false, "data-testid": `${p}meta-key`, "aria-invalid": keyError !== null || keyIssues.length > 0, "aria-describedby": keyDescribedBy || undefined }), keyError ? ((0, jsx_runtime_1.jsx)("p", { id: `${p}meta-key-error`, className: `${p}metadata-error`, "data-testid": `${p}meta-key-error`, role: "status", children: keyError })) : null, keyIssues.map((issue) => ((0, jsx_runtime_1.jsxs)("p", { id: (0, validate_1.inlineIssueId)(issue.code), className: `${p}metadata-error`, "data-testid": `${p}meta-key-validation`, role: "status", children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", children: "!" }), " ", issue.message] }, issue.code)))] }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("label", { className: `${p}metadata-label`, htmlFor: `${p}meta-name`, children: "Name" }), (0, jsx_runtime_1.jsx)("input", { id: `${p}meta-name`, className: `${p}metadata-input`, type: "text", value: metadata.name, onChange: (event) => patch({ name: event.target.value }), "data-testid": `${p}meta-name`, "aria-invalid": nameIssues.length > 0, "aria-describedby": nameDescribedBy || undefined }), nameIssues.map((issue) => ((0, jsx_runtime_1.jsxs)("p", { id: (0, validate_1.inlineIssueId)(issue.code), className: `${p}metadata-error`, "data-testid": `${p}meta-name-validation`, role: "status", children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", children: "!" }), " ", issue.message] }, issue.code)))] }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("label", { className: `${p}metadata-label`, htmlFor: `${p}meta-description`, children: "Description" }), (0, jsx_runtime_1.jsx)("textarea", { id: `${p}meta-description`, className: `${p}metadata-textarea`, value: metadata.description ?? '', onChange: (event) => {
47
59
  const description = event.target.value;
48
60
  if (description === '') {
49
61
  const { description: _removed, ...rest } = metadata;
@@ -51,6 +63,6 @@ function MetadataPanel({ metadata, previewData, onChange, }) {
51
63
  return;
52
64
  }
53
65
  patch({ description });
54
- }, rows: 3, "data-testid": `${p}meta-description` })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("label", { className: `${p}metadata-label`, htmlFor: `${p}meta-subject`, children: "Subject" }), (0, jsx_runtime_1.jsx)("input", { id: `${p}meta-subject`, ref: subjectRef, className: `${p}metadata-input`, type: "text", value: metadata.subject, onChange: (event) => patch({ subject: event.target.value }), onFocus: subjectFocus.onFocus, onBlur: subjectFocus.onBlur, "data-testid": `${p}meta-subject` }), (0, jsx_runtime_1.jsxs)("p", { className: `${p}metadata-subject-preview`, "data-testid": `${p}meta-subject-preview`, children: ["Preview: ", subjectPreview] })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("span", { className: `${p}metadata-label`, children: "Schema version" }), (0, jsx_runtime_1.jsx)("span", { className: `${p}metadata-readonly`, "data-testid": `${p}meta-schema-version`, children: metadata.schemaVersion })] })] }));
66
+ }, rows: 3, "data-testid": `${p}meta-description` })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("label", { className: `${p}metadata-label`, htmlFor: `${p}meta-subject`, children: "Subject" }), (0, jsx_runtime_1.jsx)("input", { id: `${p}meta-subject`, ref: subjectRef, className: `${p}metadata-input`, type: "text", value: metadata.subject, onChange: (event) => patch({ subject: event.target.value }), onFocus: subjectFocus.onFocus, onBlur: subjectFocus.onBlur, "data-testid": `${p}meta-subject`, "aria-invalid": subjectIssues.length > 0, "aria-describedby": subjectDescribedBy || undefined }), subjectIssues.map((issue) => ((0, jsx_runtime_1.jsxs)("p", { id: (0, validate_1.inlineIssueId)(issue.code, issue.variable), className: `${p}metadata-error`, "data-testid": `${p}meta-subject-validation`, role: "status", children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", children: "!" }), " ", issue.message] }, `${issue.code}:${issue.variable ?? ''}`))), (0, jsx_runtime_1.jsxs)("p", { className: `${p}metadata-subject-preview`, "data-testid": `${p}meta-subject-preview`, children: ["Preview: ", subjectPreview] })] }), (0, jsx_runtime_1.jsxs)("div", { className: `${p}metadata-field`, children: [(0, jsx_runtime_1.jsx)("span", { className: `${p}metadata-label`, children: "Schema version" }), (0, jsx_runtime_1.jsx)("span", { className: `${p}metadata-readonly`, "data-testid": `${p}meta-schema-version`, children: metadata.schemaVersion })] })] }));
55
67
  }
56
68
  //# sourceMappingURL=MetadataPanel.js.map