@digital-gravy/etch-public-api 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,13 +26,13 @@ Acquire it with `getEtch()`, guarded by `isEtchAvailable()` for code that might
26
26
  run outside the builder:
27
27
 
28
28
  ```ts
29
- import { getEtch, isEtchAvailable } from "@digital-gravy/etch-public-api";
29
+ import { getEtch, isEtchAvailable } from '@digital-gravy/etch-public-api';
30
30
 
31
31
  function run() {
32
- if (!isEtchAvailable()) return; // not running inside the Etch builder
33
- const etch = getEtch();
34
- const textIds = etch.blocks.find({ type: "text" });
35
- etch.blocks.setText(textIds[0], "Hello world");
32
+ if (!isEtchAvailable()) return; // not running inside the Etch builder
33
+ const etch = getEtch();
34
+ const textIds = etch.blocks.find({ type: 'text' });
35
+ etch.blocks.setText(textIds[0], 'Hello world');
36
36
  }
37
37
  ```
38
38
 
@@ -41,15 +41,16 @@ isn't on the page, so you can also `try`/`catch` it. If your script may run
41
41
  before the builder has finished loading, wait until it appears:
42
42
 
43
43
  ```ts
44
- import { getEtch, isEtchAvailable } from "@digital-gravy/etch-public-api";
44
+ import { getEtch, isEtchAvailable } from '@digital-gravy/etch-public-api';
45
45
 
46
46
  async function whenEtchReady(timeoutMs = 10_000) {
47
- const start = Date.now();
48
- while (!isEtchAvailable()) {
49
- if (Date.now() - start > timeoutMs) throw new Error("Etch did not load");
50
- await new Promise((resolve) => setTimeout(resolve, 100));
51
- }
52
- return getEtch();
47
+ const start = Date.now();
48
+ while (!isEtchAvailable()) {
49
+ if (Date.now() - start > timeoutMs)
50
+ throw new Error('Etch did not load');
51
+ await new Promise((resolve) => setTimeout(resolve, 100));
52
+ }
53
+ return getEtch();
53
54
  }
54
55
 
55
56
  const etch = await whenEtchReady();
@@ -59,12 +60,12 @@ const etch = await whenEtchReady();
59
60
 
60
61
  ```ts
61
62
  // Read
62
- const textIds = etch.blocks.find({ type: "etch/text" });
63
+ const textIds = etch.blocks.find({ type: 'etch/text' });
63
64
  const json = etch.blocks.getJson(textIds[0]);
64
65
 
65
66
  // Mutate (routes through the same guarded paths as the UI; undo/redo works)
66
- etch.blocks.setText(textIds[0], "Hello world");
67
- etch.blocks.addClass(textIds[0], "lead");
67
+ etch.blocks.setText(textIds[0], 'Hello world');
68
+ etch.blocks.addClass(textIds[0], 'lead');
68
69
 
69
70
  // Persist (blocks/styles wait for save; stylesheets/components/fields persist immediately)
70
71
  await etch.saveAsync();
@@ -74,22 +75,22 @@ await etch.saveAsync();
74
75
 
75
76
  ```ts
76
77
  // Create a class or id rule
77
- const styleId = etch.styles.create(".lead", "font-size: 1.25rem;");
78
+ const styleId = etch.styles.create('.lead', 'font-size: 1.25rem;');
78
79
 
79
80
  // Find existing styles by selector type
80
- const classStyles = etch.styles.list({ type: "class" });
81
- const myStyle = etch.styles.list().find((s) => s.selector === ".lead");
81
+ const classStyles = etch.styles.list({ type: 'class' });
82
+ const myStyle = etch.styles.list().find((s) => s.selector === '.lead');
82
83
  console.log(myStyle?.id); // the id you pass to blocks.addClass etc.
83
84
 
84
85
  // Global CSS custom properties (default collection)
85
- etch.styles.setVariable("--brand", "#0af");
86
- etch.styles.getVariable("--brand"); // "#0af"
86
+ etch.styles.setVariable('--brand', '#0af');
87
+ etch.styles.getVariable('--brand'); // "#0af"
87
88
 
88
89
  // Variable methods accept an optional collection for multi-collection :root setups
89
- etch.styles.setVariable("--brand", "#0af", "theme-a");
90
- etch.styles.getVariable("--brand", "theme-a"); // "#0af"
91
- etch.styles.listVariables("theme-a");
92
- etch.styles.removeVariable("--brand", "theme-a");
90
+ etch.styles.setVariable('--brand', '#0af', 'theme-a');
91
+ etch.styles.getVariable('--brand', 'theme-a'); // "#0af"
92
+ etch.styles.listVariables('theme-a');
93
+ etch.styles.removeVariable('--brand', 'theme-a');
93
94
  ```
94
95
 
95
96
  > **Note:** The `collection` field on style **objects** (`StyleSummary`) and the
@@ -106,7 +107,7 @@ for direct inspection or mutation, then save and exit when done:
106
107
 
107
108
  ```ts
108
109
  // Find a component block
109
- const [compId] = etch.blocks.find({ type: "etch/component" });
110
+ const [compId] = etch.blocks.find({ type: 'etch/component' });
110
111
 
111
112
  // Enter edit mode — the component's block tree becomes accessible
112
113
  etch.blocks.enterComponentEditMode(compId);
@@ -128,20 +129,47 @@ To discard in-memory changes and restore the original block:
128
129
  etch.blocks.exitComponentEditMode({ revert: true });
129
130
  ```
130
131
 
132
+ ### Component props
133
+
134
+ A component **instance** exposes its bound props as block attributes — set them
135
+ with `setAttribute` / `update`, read them with `getAttribute`. Unlike an HTML
136
+ block's free-form attributes, a component's props are a **closed schema**: the
137
+ key must be a property declared by the component definition, or the call throws
138
+ `INVALID_ARGUMENT`.
139
+
140
+ ```ts
141
+ const [cardId] = etch.blocks.find({ type: 'etch/component' });
142
+
143
+ // Set a prop the component declares
144
+ etch.blocks.setAttribute(cardId, 'title', 'Hello world');
145
+
146
+ // Read it back
147
+ etch.blocks.getAttribute(cardId, 'title'); // "Hello world"
148
+
149
+ // Or patch several props at once
150
+ etch.blocks.update(cardId, { attributes: { title: 'Hi', variant: 'primary' } });
151
+
152
+ // An undeclared key is rejected
153
+ etch.blocks.setAttribute(cardId, 'notAProp', 'x'); // ✗ throws INVALID_ARGUMENT
154
+ ```
155
+
156
+ > Values are stored as-is. For typed props such as classes or repeater/group
157
+ > data, pass the value in the component's internal format.
158
+
131
159
  ### Error handling
132
160
 
133
161
  Methods throw a typed `EtchApiError` with a `code`, rather than returning
134
162
  sentinels:
135
163
 
136
164
  ```ts
137
- import { isEtchApiError } from "@digital-gravy/etch-public-api";
165
+ import { isEtchApiError } from '@digital-gravy/etch-public-api';
138
166
 
139
167
  try {
140
- etch.blocks.getJson("does-not-exist");
168
+ etch.blocks.getJson('does-not-exist');
141
169
  } catch (err) {
142
- if (isEtchApiError(err)) {
143
- console.warn(err.code, err.message); // e.g. "BLOCK_NOT_FOUND"
144
- }
170
+ if (isEtchApiError(err)) {
171
+ console.warn(err.code, err.message); // e.g. "BLOCK_NOT_FOUND"
172
+ }
145
173
  }
146
174
  ```
147
175
 
@@ -155,8 +183,8 @@ experimental, **prefer feature detection** over version comparison:
155
183
 
156
184
  ```ts
157
185
  const etch = getEtch();
158
- if (typeof etch.blocks.someNewMethod === "function") {
159
- // safe to use
186
+ if (typeof etch.blocks.someNewMethod === 'function') {
187
+ // safe to use
160
188
  }
161
189
  ```
162
190
 
@@ -167,7 +195,7 @@ targets, and failing fast when the runtime can't satisfy it:
167
195
 
168
196
  ```ts
169
197
  // Reserved API — shape of versioned access once the contract is stable:
170
- const etch = getEtch({ apiVersion: "^1.0", id: "my-plugin" });
198
+ const etch = getEtch({ apiVersion: '^1.0', id: 'my-plugin' });
171
199
  // └─ delegates to window.etch.connect({ apiVersion: "^1.0", id }) when present,
172
200
  // yielding a version-pinned instance (throws on an incompatible runtime).
173
201
  ```
@@ -185,19 +213,19 @@ results narrow by `type`:
185
213
  ```ts
186
214
  const block = etch.blocks.getJson(id);
187
215
 
188
- if (block.type === "etch/text") {
189
- console.log(block.text); // narrowed to the text-block shape
190
- } else if (block.type === "etch/element") {
191
- console.log(block.tag, block.attributes);
216
+ if (block.type === 'etch/text') {
217
+ console.log(block.text); // narrowed to the text-block shape
218
+ } else if (block.type === 'etch/element') {
219
+ console.log(block.tag, block.attributes);
192
220
  }
193
221
 
194
222
  // Authoring is checked too — this is a type error (an `etch/text` has no `tag`):
195
223
  etch.blocks.create({
196
- type: "etch/text",
197
- version: 1,
198
- context: {},
199
- children: [],
200
- tag: "div", // ✗ type error
224
+ type: 'etch/text',
225
+ version: 1,
226
+ context: {},
227
+ children: [],
228
+ tag: 'div' // ✗ type error
201
229
  });
202
230
  ```
203
231
 
@@ -206,22 +234,24 @@ etch.blocks.create({
206
234
  Some block types recognise **special attributes** in addition to standard HTML:
207
235
 
208
236
  **`etch/dynamic-image`** — rendered as `<img>`:
237
+
209
238
  - `mediaId` — WordPress attachment ID. Etch fetches the media object and uses its URL as `src`, overriding any explicit `src`. Supports dynamic expressions (e.g. `{post.featured_image_id}`).
210
239
  - `useSrcSet` — `"true"` to generate a responsive `srcset` from the media (requires `mediaId`).
211
240
  - `maximumSize` — WordPress image size slug (e.g. `"large"`, `"full"`) used when resolving the image. Defaults to `"full"`.
212
241
 
213
242
  ```ts
214
- etch.blocks.setAttribute(imgBlockId, "mediaId", "{post.featured_image_id}");
215
- etch.blocks.setAttribute(imgBlockId, "useSrcSet", "true");
243
+ etch.blocks.setAttribute(imgBlockId, 'mediaId', '{post.featured_image_id}');
244
+ etch.blocks.setAttribute(imgBlockId, 'useSrcSet', 'true');
216
245
  ```
217
246
 
218
247
  **`etch/svg`** — inline SVG:
248
+
219
249
  - `src` — URL of an external `.svg` file. Etch fetches and inlines the SVG at render time. Supports dynamic expressions.
220
250
  - `stripColors` — `"true"` to strip `fill` and `stroke` colour declarations from the fetched SVG, so CSS can drive its colours instead.
221
251
 
222
252
  ```ts
223
- etch.blocks.setAttribute(svgBlockId, "src", "/icons/logo.svg");
224
- etch.blocks.setAttribute(svgBlockId, "stripColors", "true");
253
+ etch.blocks.setAttribute(svgBlockId, 'src', '/icons/logo.svg');
254
+ etch.blocks.setAttribute(svgBlockId, 'stripColors', 'true');
225
255
  ```
226
256
 
227
257
  ### Types only
@@ -231,9 +261,9 @@ directly:
231
261
 
232
262
  ```ts
233
263
  import type {
234
- PublicBlockJson,
235
- EtchBlocksApi,
236
- } from "@digital-gravy/etch-public-api";
264
+ PublicBlockJson,
265
+ EtchBlocksApi
266
+ } from '@digital-gravy/etch-public-api';
237
267
  ```
238
268
 
239
269
  You can also work against the global directly — the package augments
@@ -246,14 +276,14 @@ is imported.
246
276
  - `EtchApiError` / `isEtchApiError()` / `EtchApiErrorCode` — typed errors.
247
277
  - `ETCH_API_VERSION` — the contract version this package targets (`0.x`).
248
278
  - The full contract as exported types:
249
- - **Blocks** — `Etch`, `EtchBlocksApi`, `EtchBlockJson`, `PublicBlockJson`, `FindBlocksPredicate`, `BlockPatch`, …
250
- - **Styles** — `EtchStylesApi`, `StyleSummary`, `StyleListFilter`, `StyleSelectorType`, `StylePatch`
251
- - **Stylesheets** — `EtchStylesheetsApi`, `StylesheetSummary`, `StylesheetInput`, `StylesheetPatch`
252
- - **Components** — `EtchComponentsApi`, `PublicComponentSummary`, `PublicComponentJson`, `ComponentPatch`, `ComponentProperty`, …
253
- - **Loops** — `EtchLoopsApi`, `EtchLoop`, `EtchLoopConfig`, `BlockLoopBinding`, …
254
- - **Navigation** — `EtchNavigationApi`, `NavigationPlace`, `PostSummary`, `TemplateSummary`
255
- - **Fields** — `EtchFieldsApi`, `CustomField`, `CustomFieldGroup`, …
256
- - **UI / History** — `EtchUiApi`, `EtchHistoryApi`, `ColorScheme`
279
+ - **Blocks** — `Etch`, `EtchBlocksApi`, `EtchBlockJson`, `PublicBlockJson`, `FindBlocksPredicate`, `BlockPatch`, …
280
+ - **Styles** — `EtchStylesApi`, `StyleSummary`, `StyleListFilter`, `StyleSelectorType`, `StylePatch`
281
+ - **Stylesheets** — `EtchStylesheetsApi`, `StylesheetSummary`, `StylesheetInput`, `StylesheetPatch`
282
+ - **Components** — `EtchComponentsApi`, `PublicComponentSummary`, `PublicComponentJson`, `ComponentPatch`, `ComponentProperty`, …
283
+ - **Loops** — `EtchLoopsApi`, `EtchLoop`, `EtchLoopConfig`, `BlockLoopBinding`, …
284
+ - **Navigation** — `EtchNavigationApi`, `NavigationPlace`, `PostSummary`, `TemplateSummary`
285
+ - **Fields** — `EtchFieldsApi`, `CustomField`, `CustomFieldGroup`, …
286
+ - **UI / History** — `EtchUiApi`, `EtchHistoryApi`, `ColorScheme`
257
287
 
258
288
  ## Versioning
259
289
 
package/dist/index.cjs CHANGED
@@ -47,7 +47,10 @@ function getEtch(options = {}) {
47
47
  return etch.connect(options);
48
48
  }
49
49
  if (options.apiVersion) {
50
- warnIfIncompatible(options.apiVersion, etch.apiVersion ?? ETCH_API_VERSION);
50
+ warnIfIncompatible(
51
+ options.apiVersion,
52
+ etch.apiVersion ?? ETCH_API_VERSION
53
+ );
51
54
  }
52
55
  return etch;
53
56
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts"],"names":[],"mappings":";;;AAyBO,IAAM,YAAA,GAAN,cAA2B,KAAA,CAAM;AAAA,EAGvC,WAAA,CAAY,MAAwB,OAAA,EAAiB;AACpD,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACb;AACD;AAGO,SAAS,eAAe,KAAA,EAAuC;AACrE,EAAA,OAAO,KAAA,YAAiB,YAAA,IAAiB,KAAA,EAAiB,IAAA,KAAS,cAAA;AACpE;;;AC9BO,IAAM,gBAAA,GAAmB;;;ACIhC,SAAS,QAAA,GAA6B;AACrC,EAAA,MAAM,KAAA,GAAQ,UAAA;AACd,EAAA,OAAO,KAAA,CAAM,IAAA;AACd;AAMO,SAAS,eAAA,GAA2B;AAC1C,EAAA,OAAO,UAAS,KAAM,MAAA;AACvB;AAGA,SAAS,QAAQ,OAAA,EAAgC;AAChD,EAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,IAAA,CAAK,OAAO,CAAA;AACtC,EAAA,OAAO,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA,GAAI,IAAA;AACnC;AAOA,SAAS,kBAAA,CAAmB,WAAmB,OAAA,EAAuB;AACrE,EAAA,MAAM,IAAA,GAAO,QAAQ,SAAS,CAAA;AAC9B,EAAA,MAAM,IAAA,GAAO,QAAQ,OAAO,CAAA;AAC5B,EAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,IAAA,IAAQ,SAAS,IAAA,EAAM;AAErD,EAAA,OAAA,CAAQ,IAAA;AAAA,IACP,CAAA,qDAAA,EAAwD,SAAS,CAAA,wBAAA,EAC9C,OAAO,CAAA,sBAAA;AAAA,GAC3B;AACD;AAsBO,SAAS,OAAA,CAAQ,OAAA,GAA0B,EAAC,EAAS;AAC3D,EAAA,MAAM,OAAO,QAAA,EAAS;AACtB,EAAA,IAAI,CAAC,IAAA,EAAM;AACV,IAAA,MAAM,IAAI,YAAA;AAAA,MACT,eAAA;AAAA,MACA;AAAA,KAED;AAAA,EACD;AAEA,EAAA,IAAI,OAAO,IAAA,CAAK,OAAA,KAAY,UAAA,EAAY;AACvC,IAAA,OAAO,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,EAC5B;AAEA,EAAA,IAAI,QAAQ,UAAA,EAAY;AACvB,IAAA,kBAAA,CAAmB,OAAA,CAAQ,UAAA,EAAY,IAAA,CAAK,UAAA,IAAc,gBAAgB,CAAA;AAAA,EAC3E;AACA,EAAA,OAAO,IAAA;AACR","file":"index.cjs","sourcesContent":["/**\n * Error codes thrown by the public `window.etch` API.\n *\n * The API throws typed errors (rather than returning sentinels) so that AI\n * assistants and plugin authors can `try`/`catch` and react to a precise cause.\n *\n * The union ends with `(string & {})` so that codes added by newer Etch\n * runtimes still type-check as `EtchApiErrorCode` while keeping autocomplete for\n * the known values.\n */\nexport type EtchApiErrorCode =\n\t| 'BLOCK_NOT_FOUND'\n\t| 'WRONG_BLOCK_TYPE'\n\t| 'READONLY'\n\t| 'INVALID_ARGUMENT'\n\t| 'LOOP_NOT_FOUND'\n\t| 'STYLE_NOT_FOUND'\n\t| 'STYLESHEET_NOT_FOUND'\n\t| 'COMPONENT_NOT_FOUND'\n\t| 'POST_NOT_FOUND'\n\t| 'OPERATION_FAILED'\n\t| 'NOT_AVAILABLE'\n\t| (string & {});\n\n/** Error thrown by the public Etch API (and by this client). */\nexport class EtchApiError extends Error {\n\treadonly code: EtchApiErrorCode;\n\n\tconstructor(code: EtchApiErrorCode, message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'EtchApiError';\n\t\tthis.code = code;\n\t}\n}\n\n/** Narrow an unknown caught value to an {@link EtchApiError}. */\nexport function isEtchApiError(value: unknown): value is EtchApiError {\n\treturn value instanceof EtchApiError || (value as Error)?.name === 'EtchApiError';\n}\n","/**\n * Version of the Etch scripting **contract** this package targets, independent\n * of the Etch product version and of this package's own npm version.\n *\n * `0.x` signals the surface is **experimental** and may change without a major\n * bump until it stabilizes. It matches the value returned by the runtime's\n * {@link Etch.apiVersion} getter on `window.etch`.\n */\nexport const ETCH_API_VERSION = '0.x';\n","import type { ConnectOptions, Etch } from './contract';\nimport { EtchApiError } from './errors';\nimport { ETCH_API_VERSION } from './version';\n\ndeclare global {\n\tinterface Window {\n\t\t/** The Etch scripting API, present once the builder has loaded. */\n\t\tetch?: Etch;\n\t}\n}\n\n/** Read `window.etch` from whatever global object exists, or `undefined`. */\nfunction readEtch(): Etch | undefined {\n\tconst scope = globalThis as { etch?: Etch };\n\treturn scope.etch;\n}\n\n/**\n * Whether the Etch scripting API is present on the page. Use this to guard code\n * that should no-op when not running inside the builder.\n */\nexport function isEtchAvailable(): boolean {\n\treturn readEtch() !== undefined;\n}\n\n/** The major version number of a semver-ish string, or `null` if unparseable. */\nfunction majorOf(version: string): number | null {\n\tconst match = /^\\D*(\\d+)/.exec(version);\n\treturn match ? Number(match[1]) : null;\n}\n\n/**\n * Best-effort compatibility check for `0.x` runtimes that have no native\n * `connect()`. Warns (does not throw) when the requested major differs from the\n * runtime's, so experimental consumers aren't hard-blocked.\n */\nfunction warnIfIncompatible(requested: string, runtime: string): void {\n\tconst want = majorOf(requested);\n\tconst have = majorOf(runtime);\n\tif (want === null || have === null || want === have) return;\n\t// eslint-disable-next-line no-console\n\tconsole.warn(\n\t\t`[@digital-gravy/etch-public-api] Requested Etch API v${requested} but the ` +\n\t\t\t`page provides v${runtime}. Behavior may differ.`\n\t);\n}\n\n/**\n * Acquire the Etch scripting API from the page.\n *\n * The runtime lives on `window.etch` (injected by the builder); this returns it\n * typed. When the page exposes a future stable runtime with a native\n * `connect()`, version negotiation is delegated to it. On today's `0.x`\n * runtime, the global is returned directly after a best-effort version check.\n *\n * @throws {EtchApiError} `NOT_AVAILABLE` when the builder is not present.\n *\n * @example\n * ```ts\n * import { getEtch } from '@etchwp/public-api';\n *\n * const etch = getEtch();\n * const ids = etch.blocks.find({ type: 'text' });\n * etch.blocks.setText(ids[0], 'Hello');\n * await etch.saveAsync();\n * ```\n */\nexport function getEtch(options: ConnectOptions = {}): Etch {\n\tconst etch = readEtch();\n\tif (!etch) {\n\t\tthrow new EtchApiError(\n\t\t\t'NOT_AVAILABLE',\n\t\t\t'window.etch is not available. The Etch builder is not loaded on this page, ' +\n\t\t\t\t'or getEtch() ran before it finished initializing.'\n\t\t);\n\t}\n\n\tif (typeof etch.connect === 'function') {\n\t\treturn etch.connect(options);\n\t}\n\n\tif (options.apiVersion) {\n\t\twarnIfIncompatible(options.apiVersion, etch.apiVersion ?? ETCH_API_VERSION);\n\t}\n\treturn etch;\n}\n"]}
1
+ {"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/client.ts"],"names":[],"mappings":";;;AAyBO,IAAM,YAAA,GAAN,cAA2B,KAAA,CAAM;AAAA,EAGvC,WAAA,CAAY,MAAwB,OAAA,EAAiB;AACpD,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,cAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACb;AACD;AAGO,SAAS,eAAe,KAAA,EAAuC;AACrE,EAAA,OACC,KAAA,YAAiB,YAAA,IAChB,KAAA,EAAiB,IAAA,KAAS,cAAA;AAE7B;;;ACjCO,IAAM,gBAAA,GAAmB;;;ACIhC,SAAS,QAAA,GAA6B;AACrC,EAAA,MAAM,KAAA,GAAQ,UAAA;AACd,EAAA,OAAO,KAAA,CAAM,IAAA;AACd;AAMO,SAAS,eAAA,GAA2B;AAC1C,EAAA,OAAO,UAAS,KAAM,MAAA;AACvB;AAGA,SAAS,QAAQ,OAAA,EAAgC;AAChD,EAAA,MAAM,KAAA,GAAQ,WAAA,CAAY,IAAA,CAAK,OAAO,CAAA;AACtC,EAAA,OAAO,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,CAAA,GAAI,IAAA;AACnC;AAOA,SAAS,kBAAA,CAAmB,WAAmB,OAAA,EAAuB;AACrE,EAAA,MAAM,IAAA,GAAO,QAAQ,SAAS,CAAA;AAC9B,EAAA,MAAM,IAAA,GAAO,QAAQ,OAAO,CAAA;AAC5B,EAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,IAAA,KAAS,IAAA,IAAQ,SAAS,IAAA,EAAM;AACrD,EAAA,OAAA,CAAQ,IAAA;AAAA,IACP,CAAA,qDAAA,EAAwD,SAAS,CAAA,wBAAA,EAC9C,OAAO,CAAA,sBAAA;AAAA,GAC3B;AACD;AAsBO,SAAS,OAAA,CAAQ,OAAA,GAA0B,EAAC,EAAS;AAC3D,EAAA,MAAM,OAAO,QAAA,EAAS;AACtB,EAAA,IAAI,CAAC,IAAA,EAAM;AACV,IAAA,MAAM,IAAI,YAAA;AAAA,MACT,eAAA;AAAA,MACA;AAAA,KAED;AAAA,EACD;AAEA,EAAA,IAAI,OAAO,IAAA,CAAK,OAAA,KAAY,UAAA,EAAY;AACvC,IAAA,OAAO,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,EAC5B;AAEA,EAAA,IAAI,QAAQ,UAAA,EAAY;AACvB,IAAA,kBAAA;AAAA,MACC,OAAA,CAAQ,UAAA;AAAA,MACR,KAAK,UAAA,IAAc;AAAA,KACpB;AAAA,EACD;AACA,EAAA,OAAO,IAAA;AACR","file":"index.cjs","sourcesContent":["/**\n * Error codes thrown by the public `window.etch` API.\n *\n * The API throws typed errors (rather than returning sentinels) so that AI\n * assistants and plugin authors can `try`/`catch` and react to a precise cause.\n *\n * The union ends with `(string & {})` so that codes added by newer Etch\n * runtimes still type-check as `EtchApiErrorCode` while keeping autocomplete for\n * the known values.\n */\nexport type EtchApiErrorCode =\n\t| 'BLOCK_NOT_FOUND'\n\t| 'WRONG_BLOCK_TYPE'\n\t| 'READONLY'\n\t| 'INVALID_ARGUMENT'\n\t| 'LOOP_NOT_FOUND'\n\t| 'STYLE_NOT_FOUND'\n\t| 'STYLESHEET_NOT_FOUND'\n\t| 'COMPONENT_NOT_FOUND'\n\t| 'POST_NOT_FOUND'\n\t| 'OPERATION_FAILED'\n\t| 'NOT_AVAILABLE'\n\t| (string & {});\n\n/** Error thrown by the public Etch API (and by this client). */\nexport class EtchApiError extends Error {\n\treadonly code: EtchApiErrorCode;\n\n\tconstructor(code: EtchApiErrorCode, message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'EtchApiError';\n\t\tthis.code = code;\n\t}\n}\n\n/** Narrow an unknown caught value to an {@link EtchApiError}. */\nexport function isEtchApiError(value: unknown): value is EtchApiError {\n\treturn (\n\t\tvalue instanceof EtchApiError ||\n\t\t(value as Error)?.name === 'EtchApiError'\n\t);\n}\n","/**\n * Version of the Etch scripting **contract** this package targets, independent\n * of the Etch product version and of this package's own npm version.\n *\n * `0.x` signals the surface is **experimental** and may change without a major\n * bump until it stabilizes. It matches the value returned by the runtime's\n * {@link Etch.apiVersion} getter on `window.etch`.\n */\nexport const ETCH_API_VERSION = '0.x';\n","import type { ConnectOptions, Etch } from './contract';\nimport { EtchApiError } from './errors';\nimport { ETCH_API_VERSION } from './version';\n\ndeclare global {\n\tinterface Window {\n\t\t/** The Etch scripting API, present once the builder has loaded. */\n\t\tetch?: Etch;\n\t}\n}\n\n/** Read `window.etch` from whatever global object exists, or `undefined`. */\nfunction readEtch(): Etch | undefined {\n\tconst scope = globalThis as { etch?: Etch };\n\treturn scope.etch;\n}\n\n/**\n * Whether the Etch scripting API is present on the page. Use this to guard code\n * that should no-op when not running inside the builder.\n */\nexport function isEtchAvailable(): boolean {\n\treturn readEtch() !== undefined;\n}\n\n/** The major version number of a semver-ish string, or `null` if unparseable. */\nfunction majorOf(version: string): number | null {\n\tconst match = /^\\D*(\\d+)/.exec(version);\n\treturn match ? Number(match[1]) : null;\n}\n\n/**\n * Best-effort compatibility check for `0.x` runtimes that have no native\n * `connect()`. Warns (does not throw) when the requested major differs from the\n * runtime's, so experimental consumers aren't hard-blocked.\n */\nfunction warnIfIncompatible(requested: string, runtime: string): void {\n\tconst want = majorOf(requested);\n\tconst have = majorOf(runtime);\n\tif (want === null || have === null || want === have) return;\n\tconsole.warn(\n\t\t`[@digital-gravy/etch-public-api] Requested Etch API v${requested} but the ` +\n\t\t\t`page provides v${runtime}. Behavior may differ.`\n\t);\n}\n\n/**\n * Acquire the Etch scripting API from the page.\n *\n * The runtime lives on `window.etch` (injected by the builder); this returns it\n * typed. When the page exposes a future stable runtime with a native\n * `connect()`, version negotiation is delegated to it. On today's `0.x`\n * runtime, the global is returned directly after a best-effort version check.\n *\n * @throws {EtchApiError} `NOT_AVAILABLE` when the builder is not present.\n *\n * @example\n * ```ts\n * import { getEtch } from '@etchwp/public-api';\n *\n * const etch = getEtch();\n * const ids = etch.blocks.find({ type: 'text' });\n * etch.blocks.setText(ids[0], 'Hello');\n * await etch.saveAsync();\n * ```\n */\nexport function getEtch(options: ConnectOptions = {}): Etch {\n\tconst etch = readEtch();\n\tif (!etch) {\n\t\tthrow new EtchApiError(\n\t\t\t'NOT_AVAILABLE',\n\t\t\t'window.etch is not available. The Etch builder is not loaded on this page, ' +\n\t\t\t\t'or getEtch() ran before it finished initializing.'\n\t\t);\n\t}\n\n\tif (typeof etch.connect === 'function') {\n\t\treturn etch.connect(options);\n\t}\n\n\tif (options.apiVersion) {\n\t\twarnIfIncompatible(\n\t\t\toptions.apiVersion,\n\t\t\tetch.apiVersion ?? ETCH_API_VERSION\n\t\t);\n\t}\n\treturn etch;\n}\n"]}