@datocms/astro 0.6.11 → 0.6.12

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@datocms/astro",
3
3
  "description": "A set of components and utilities to work faster with DatoCMS in Astro projects.",
4
4
  "type": "module",
5
- "version": "0.6.11",
5
+ "version": "0.6.12",
6
6
  "sideEffects": false,
7
7
  "repository": {
8
8
  "type": "git",
@@ -46,7 +46,7 @@
46
46
  ],
47
47
  "dependencies": {
48
48
  "@0no-co/graphql.web": "^1.0.7",
49
- "@datocms/content-link": "^0.3.13",
49
+ "@datocms/content-link": "^0.3.19",
50
50
  "datocms-listen": "^1.0.1",
51
51
  "datocms-structured-text-generic-html-renderer": "^5.0.0",
52
52
  "datocms-structured-text-utils": "^5.1.6"
@@ -80,9 +80,16 @@ export interface Props {
80
80
  * @default false
81
81
  */
82
82
  stripStega?: boolean;
83
+
84
+ /**
85
+ * Hue (0–359) of the overlay accent color.
86
+ *
87
+ * @default 17 (orange)
88
+ */
89
+ hue?: number;
83
90
  }
84
91
 
85
- const { enableClickToEdit, stripStega = false } = Astro.props;
92
+ const { enableClickToEdit, stripStega = false, hue } = Astro.props;
86
93
 
87
94
  // Serialize props for client-side script
88
95
  const enableClickToEditValue =
@@ -98,6 +105,7 @@ const enableClickToEditValue =
98
105
  ? JSON.stringify(enableClickToEditValue)
99
106
  : undefined}
100
107
  data-strip-stega={stripStega ? 'true' : undefined}
108
+ data-hue={hue !== undefined ? String(hue) : undefined}
101
109
  >
102
110
  </datocms-content-link>
103
111
 
@@ -127,6 +135,8 @@ const enableClickToEditValue =
127
135
  ? (JSON.parse(enableClickToEditAttr) as boolean | ClickToEditOptions)
128
136
  : undefined;
129
137
  const stripStega = stripStegaAttr === 'true';
138
+ const hueAttr = this.getAttribute('data-hue');
139
+ const hue = hueAttr !== null ? Number(hueAttr) : undefined;
130
140
 
131
141
  // Initialize the content-link controller
132
142
  this.controller = createController({
@@ -138,6 +148,8 @@ const enableClickToEditValue =
138
148
  },
139
149
  // Strip stega encoding if requested
140
150
  stripStega: stripStega,
151
+ // Accent color hue for overlays
152
+ hue: hue,
141
153
  });
142
154
 
143
155
  // Notify the controller of the current path
@@ -32,18 +32,26 @@ Visual Editing transforms how editors interact with your content by letting them
32
32
  - [Usage](#usage)
33
33
  - [Props](#props)
34
34
  - [`enableClickToEdit` options](#enableclicktoedit-options)
35
- - [StructuredText integration](#structuredtext-integration)
36
- - [Edit groups with `data-datocms-content-link-group`](#edit-groups-with-data-datocms-content-link-group)
37
- - [Edit boundaries with `data-datocms-content-link-boundary`](#edit-boundaries-with-data-datocms-content-link-boundary)
38
- - [Manual overlays](#manual-overlays)
39
- - [Using `data-datocms-content-link-url`](#using-data-datocms-content-link-url)
40
- - [Using `data-datocms-content-link-source`](#using-data-datocms-content-link-source)
35
+ - [Data attributes reference](#data-attributes-reference)
36
+ - [Developer-specified attributes](#developer-specified-attributes)
37
+ - [`data-datocms-content-link-url`](#data-datocms-content-link-url)
38
+ - [`data-datocms-content-link-source`](#data-datocms-content-link-source)
39
+ - [`data-datocms-content-link-group`](#data-datocms-content-link-group)
40
+ - [`data-datocms-content-link-boundary`](#data-datocms-content-link-boundary)
41
+ - [Library-managed attributes](#library-managed-attributes)
42
+ - [`data-datocms-contains-stega`](#data-datocms-contains-stega)
43
+ - [`data-datocms-auto-content-link-url`](#data-datocms-auto-content-link-url)
44
+ - [How group and boundary resolution works](#how-group-and-boundary-resolution-works)
45
+ - [Structured Text fields](#structured-text-fields)
46
+ - [Rule 1: Always wrap the Structured Text component in a group](#rule-1-always-wrap-the-structured-text-component-in-a-group)
47
+ - [Rule 2: Wrap embedded blocks, inline blocks, and inline records in a boundary](#rule-2-wrap-embedded-blocks-inline-blocks-and-inline-records-in-a-boundary)
41
48
  - [Low-level utilities](#low-level-utilities)
42
49
  - [`stripStega()` works with any data type](#stripstega-works-with-any-data-type)
43
50
  - [Troubleshooting](#troubleshooting)
44
51
  - [Click-to-edit overlays not appearing](#click-to-edit-overlays-not-appearing)
45
52
  - [Navigation not syncing in Web Previews plugin](#navigation-not-syncing-in-web-previews-plugin)
46
53
  - [Content inside StructuredText not clickable](#content-inside-structuredtext-not-clickable)
54
+ - [Layout issues caused by stega encoding](#layout-issues-caused-by-stega-encoding)
47
55
 
48
56
  <!-- END doctoc generated TOC please keep comment here to allow auto update -->
49
57
 
@@ -145,6 +153,7 @@ You get the full Visual Editing experience regardless of your routing setup.
145
153
  | ------------------- | --------------------------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
146
154
  | `enableClickToEdit` | `boolean \| { scrollToNearestTarget?: boolean; hoverOnly?: boolean }` | - | Enable click-to-edit overlays on mount. Use `true` for immediate activation, or pass options object (see below) |
147
155
  | `stripStega` | `boolean` | `false` | Strip stega-encoded invisible characters from text content. When `true`, encoding is permanently removed (prevents controller recreation) |
156
+ | `hue` | `number` | `17` | Hue (0–359) of the overlay accent color. Default is the DatoCMS hue (`17`). Use this to match your brand or project colors |
148
157
 
149
158
  ### `enableClickToEdit` options
150
159
 
@@ -173,111 +182,289 @@ When passing an options object to `enableClickToEdit`, the following properties
173
182
 
174
183
  The `hoverOnly` option is particularly useful for websites that receive traffic from both desktop and mobile users. On touch devices, the click-to-edit overlays can interfere with normal scrolling and tapping behavior. By setting `hoverOnly: true`, overlays will only appear automatically on devices with a mouse or trackpad, while touch device users can still access click-to-edit mode by pressing and holding the Alt/Option key.
175
184
 
176
- ## StructuredText integration
185
+ ## Data attributes reference
177
186
 
178
- When working with DatoCMS's [Structured Text fields](https://www.datocms.com/docs/structured-text/dast), you may want more control over which areas are clickable.
187
+ This library uses several `data-datocms-*` attributes. Some are **developer-specified** (you add them to your markup), and some are **library-managed** (added automatically during DOM stamping). Here's a complete reference.
179
188
 
180
- ### Edit groups with `data-datocms-content-link-group`
189
+ ### Developer-specified attributes
181
190
 
182
- By default, only the specific element containing stega-encoded data is clickable. For Structured Text fields, this might be a single `<span>` inside a paragraph, which creates a poor editing experience.
191
+ These attributes are added by you in your templates/components to control how editable regions behave.
183
192
 
184
- Use the `data-datocms-content-link-group` attribute to make a larger area clickable:
193
+ #### `data-datocms-content-link-url`
194
+
195
+ Manually marks an element as editable with an explicit edit URL. Use this for non-text fields (booleans, numbers, dates, JSON) that cannot contain stega encoding. The recommended approach is to use the `_editingUrl` field available on all records:
196
+
197
+ ```graphql
198
+ query {
199
+ product {
200
+ id
201
+ price
202
+ isActive
203
+ _editingUrl
204
+ }
205
+ }
206
+ ```
185
207
 
186
208
  ```astro
187
- ---
188
- import { StructuredText } from '@datocms/astro/StructuredText';
189
- ---
209
+ <span data-datocms-content-link-url={product._editingUrl}>
210
+ ${product.price}
211
+ </span>
212
+ ```
213
+
214
+ #### `data-datocms-content-link-source`
215
+
216
+ Attaches stega-encoded metadata without the need to render it as content. Useful for structural elements that cannot contain text (like `<video>`, `<audio>`, `<iframe>`, etc.) or when stega encoding in visible text would be problematic:
217
+
218
+ ```astro
219
+ <div data-datocms-content-link-source={video.alt}>
220
+ <video src={video.url} poster={video.posterImage.url} controls></video>
221
+ </div>
222
+ ```
223
+
224
+ The value must be a stega-encoded string (any text field from the API will work). The library decodes the stega metadata from the attribute value and makes the element clickable to edit.
225
+
226
+ #### `data-datocms-content-link-group`
227
+
228
+ Expands the clickable area to a parent element. When the library encounters stega-encoded content, by default it makes the immediate parent of the text node clickable to edit. Adding this attribute to an ancestor makes that ancestor the clickable target instead:
229
+
230
+ ```html
231
+ <article data-datocms-content-link-group>
232
+ <!-- product.title contains stega encoding -->
233
+ <h2>{product.title}</h2>
234
+ <p>${product.price}</p>
235
+ </article>
236
+ ```
237
+
238
+ Here, clicking anywhere in the `<article>` opens the editor, rather than requiring users to click precisely on the `<h2>`.
239
+
240
+ **Important:** A group should contain only one stega-encoded source. If multiple stega strings resolve to the same group, the library logs a collision warning and only the last URL wins.
241
+
242
+ #### `data-datocms-content-link-boundary`
243
+
244
+ Stops the upward DOM traversal that looks for a `data-datocms-content-link-group`, making the element where stega was found the clickable target instead. This creates an independent editable region that won't merge into a parent group (see [How group and boundary resolution works](#how-group-and-boundary-resolution-works) below for details):
190
245
 
191
- <!-- Make the entire structured text area clickable -->
246
+ ```html
192
247
  <div data-datocms-content-link-group>
193
- <StructuredText data={content.structuredTextField} />
248
+ <!-- page.title contains stega encoding → resolves to URL A -->
249
+ <h1>{page.title}</h1>
250
+ <section data-datocms-content-link-boundary>
251
+ <!-- page.author contains stega encoding → resolves to URL B -->
252
+ <span>{page.author}</span>
253
+ </section>
194
254
  </div>
195
255
  ```
196
256
 
197
- Now editors can click anywhere within the structured text content to open the field editor.
257
+ Without the boundary, clicking `page.author` would open URL A (the outer group). With the boundary, the `<span>` becomes the clickable target opening URL B.
198
258
 
199
- ### Edit boundaries with `data-datocms-content-link-boundary`
259
+ The boundary can also be placed directly on the element that contains the stega text:
200
260
 
201
- When Structured Text contains embedded blocks or inline records, you typically want:
261
+ ```html
262
+ <div data-datocms-content-link-group>
263
+ <!-- page.title contains stega encoding → resolves to URL A -->
264
+ <h1>{page.title}</h1>
265
+ <!-- page.author contains stega encoding → resolves to URL B -->
266
+ <span data-datocms-content-link-boundary>{page.author}</span>
267
+ </div>
268
+ ```
269
+
270
+ Here, the `<span>` has the boundary and directly contains the stega text, so the `<span>` itself becomes the clickable target (since the starting element and the boundary element are the same).
271
+
272
+ ### Library-managed attributes
273
+
274
+ These attributes are added automatically by the library during DOM stamping. You do not need to add them yourself, but you can target them in CSS or JavaScript.
275
+
276
+ #### `data-datocms-contains-stega`
277
+
278
+ Added to elements whose text content contains stega-encoded invisible characters. This attribute is only present when `stripStega` is `false` (the default), since with `stripStega: true` the characters are removed entirely. Useful for CSS workarounds — the zero-width characters can sometimes cause unexpected letter-spacing or text overflow:
279
+
280
+ ```css
281
+ [data-datocms-contains-stega] {
282
+ letter-spacing: 0 !important;
283
+ }
284
+ ```
285
+
286
+ #### `data-datocms-auto-content-link-url`
287
+
288
+ Added automatically to elements that the library has identified as editable targets (through stega decoding and group/boundary resolution). Contains the resolved edit URL.
289
+
290
+ This is the automatic counterpart to the developer-specified `data-datocms-content-link-url`. The library adds `data-datocms-auto-content-link-url` wherever it can extract an edit URL from stega encoding, while `data-datocms-content-link-url` is needed for non-text fields (booleans, numbers, dates, etc.) where stega encoding cannot be embedded. Both attributes are used by the click-to-edit overlay system to determine which elements are clickable and where they link to.
291
+
292
+ ## How group and boundary resolution works
293
+
294
+ When the library encounters stega-encoded content inside an element, it walks up the DOM tree from that element:
295
+
296
+ 1. If it finds a `data-datocms-content-link-group`, it stops and stamps **that** element as the clickable target.
297
+ 2. If it finds a `data-datocms-content-link-boundary`, it stops and stamps the **starting element** as the clickable target — further traversal is prevented.
298
+ 3. If it reaches the root without finding either, it stamps the **starting element**.
299
+
300
+ Here are some concrete examples to illustrate:
301
+
302
+ **Example 1: Nested groups**
303
+
304
+ ```html
305
+ <div data-datocms-content-link-group>
306
+ <!-- page.title contains stega encoding → resolves to URL A -->
307
+ <h1>{page.title}</h1>
308
+ <div data-datocms-content-link-group>
309
+ <!-- page.subtitle contains stega encoding → resolves to URL B -->
310
+ <p>{page.subtitle}</p>
311
+ </div>
312
+ </div>
313
+ ```
314
+
315
+ - **`page.title`**: walks up from `<h1>`, finds the outer group → the **outer `<div>`** becomes clickable (opens URL A).
316
+ - **`page.subtitle`**: walks up from `<p>`, finds the inner group first → the **inner `<div>`** becomes clickable (opens URL B). The outer group is never reached.
317
+
318
+ Each nested group creates an independent clickable region. The innermost group always wins for its own content.
319
+
320
+ **Example 2: Boundary preventing group propagation**
321
+
322
+ ```html
323
+ <div data-datocms-content-link-group>
324
+ <!-- page.title contains stega encoding → resolves to URL A -->
325
+ <h1>{page.title}</h1>
326
+ <section data-datocms-content-link-boundary>
327
+ <!-- page.author contains stega encoding → resolves to URL B -->
328
+ <span>{page.author}</span>
329
+ </section>
330
+ </div>
331
+ ```
202
332
 
203
- - Main text content (paragraphs, headings, lists) to open the Structured Text field editor
204
- - Embedded blocks to open their own specific record editor
333
+ - **`page.title`**: walks up from `<h1>`, finds the outer group → the **outer `<div>`** becomes clickable (opens URL A).
334
+ - **`page.author`**: walks up from `<span>`, hits the `<section>` boundary → traversal stops, the **`<span>`** itself becomes clickable (opens URL B). The outer group is not reached.
205
335
 
206
- Use `data-datocms-content-link-boundary` to prevent click events from bubbling up past a certain point:
336
+ **Example 3: Boundary inside a group**
337
+
338
+ ```html
339
+ <div data-datocms-content-link-group>
340
+ <!-- page.description contains stega encoding → resolves to URL A -->
341
+ <p>{page.description}</p>
342
+ <div data-datocms-content-link-boundary>
343
+ <!-- page.footnote contains stega encoding → resolves to URL B -->
344
+ <p>{page.footnote}</p>
345
+ </div>
346
+ </div>
347
+ ```
348
+
349
+ - **`page.description`**: walks up from `<p>`, finds the outer group → the **outer `<div>`** becomes clickable (opens URL A).
350
+ - **`page.footnote`**: walks up from `<p>`, hits the boundary → traversal stops, the **`<p>`** itself becomes clickable (opens URL B). The outer group is not reached.
351
+
352
+ **Example 4: Multiple stega strings without groups (collision warning)**
353
+
354
+ ```html
355
+ <p>
356
+ <!-- Both product.name and product.tagline contain stega encoding -->
357
+ {product.name} {product.tagline}
358
+ </p>
359
+ ```
360
+
361
+ Both stega-encoded strings resolve to the same `<p>` element. The library logs a console warning and the last URL wins. To fix this, wrap each piece of content in its own element:
362
+
363
+ ```html
364
+ <p>
365
+ <span>{product.name}</span>
366
+ <span>{product.tagline}</span>
367
+ </p>
368
+ ```
369
+
370
+ ## Structured Text fields
371
+
372
+ Structured Text fields require special attention because of how stega encoding works within them:
373
+
374
+ - The DatoCMS API encodes stega information inside a single `<span>` within the structured text output. Without any configuration, only that small span would be clickable.
375
+ - Structured Text fields can contain **embedded blocks** and **inline records**, each with their own editing URL that should open a different record in the editor.
376
+
377
+ Here are the rules to follow:
378
+
379
+ ### Rule 1: Always wrap the Structured Text component in a group
380
+
381
+ This makes the entire structured text area clickable, instead of just the tiny stega-encoded span:
207
382
 
208
383
  ```astro
209
384
  ---
210
385
  import { StructuredText } from '@datocms/astro/StructuredText';
211
- import BlogPost from './BlogPost.astro';
212
386
  ---
213
387
 
214
388
  <div data-datocms-content-link-group>
215
- <StructuredText
216
- data={content.structuredTextField}
217
- components={{
218
- renderBlock: ({ record }) => {
219
- // This boundary prevents the block from using the parent group
220
- return (
221
- <div data-datocms-content-link-boundary>
222
- <BlogPost data={record} />
223
- </div>
224
- );
225
- },
226
- }}
227
- />
389
+ <StructuredText data={page.content} />
228
390
  </div>
229
391
  ```
230
392
 
231
- This ensures:
232
-
233
- - Clicking the main text opens the Structured Text field editor
234
- - Clicking an embedded block opens that specific block's editor
393
+ ### Rule 2: Wrap embedded blocks, inline blocks, and inline records in a boundary
235
394
 
236
- ## Manual overlays
395
+ Embedded blocks, inline blocks, and inline records have their own edit URL (pointing to the block/record). Without a boundary, clicking them would bubble up to the parent group and open the structured text field editor instead. Add `data-datocms-content-link-boundary` to prevent them from merging into the parent group.
237
396
 
238
- In some cases, you may want to manually create click-to-edit overlays for content that doesn't have stega encoding.
397
+ **Note:** Record links (`renderLinkToRecord`) don't need a boundary. They are typically just `<a>` tags wrapping text that already belongs to the surrounding structured text. Since they don't introduce a separate editing target, there's no URL collision and no reason to isolate them from the parent group — clicking a record link's text should open the structured text field editor, just like clicking any other text in the field.
239
398
 
240
- ### Using `data-datocms-content-link-url`
399
+ Add `data-datocms-content-link-boundary` to the root element of each component that renders a block, inline block, or inline record. For example, given a `Cta` block component:
241
400
 
242
- You can add the `data-datocms-content-link-url` attribute with a DatoCMS editing URL:
401
+ ```astro
402
+ ---
403
+ // src/components/Cta.astro
404
+ const { block } = Astro.props;
405
+ ---
243
406
 
244
- ```graphql
245
- query {
246
- product {
247
- id
248
- price
249
- isActive
250
- inStock
251
- _editingUrl
252
- }
253
- }
407
+ <div data-datocms-content-link-boundary>
408
+ <a href={block.url}>{block.label}</a>
409
+ </div>
254
410
  ```
255
411
 
412
+ For inline blocks, use a `<span>` instead of a `<div>` since they appear within inline content:
413
+
256
414
  ```astro
257
- <div>
258
- <span data-datocms-content-link-url={product._editingUrl}>
259
- ${product.price}
260
- </span>
261
-
262
- <span data-datocms-content-link-url={product._editingUrl}>
263
- {product.inStock ? 'In Stock' : 'Out of Stock'}
264
- </span>
265
- </div>
415
+ ---
416
+ // src/components/NewsletterSignup.astro
417
+ const { block } = Astro.props;
418
+ ---
419
+
420
+ <span data-datocms-content-link-boundary>
421
+ <input type="email" placeholder={block.placeholder} />
422
+ </span>
266
423
  ```
267
424
 
268
- ### Using `data-datocms-content-link-source`
425
+ Same for inline records:
426
+
427
+ ```astro
428
+ ---
429
+ // src/components/InlineTeamMember.astro
430
+ const { record } = Astro.props;
431
+ ---
432
+
433
+ <span data-datocms-content-link-boundary>
434
+ <a href={`/team/${record.slug}`}>{record.name}</a>
435
+ </span>
436
+ ```
269
437
 
270
- For elements without visible stega-encoded content, use the [`data-datocms-content-link-source`](https://github.com/datocms/content-link?tab=readme-ov-file#stamping-elements-via-data-datocms-content-link-source) attribute to attach stega metadata directly:
438
+ Then use these components directly in your structured text rendering:
271
439
 
272
440
  ```astro
273
- <!-- product.asset.video.alt contains stega-encoded info -->
274
- <video
275
- src={product.asset.video.url}
276
- data-datocms-content-link-source={product.asset.video.alt}
277
- controls></video>
441
+ ---
442
+ import { StructuredText } from '@datocms/astro/StructuredText';
443
+ import Cta from '~/components/Cta.astro';
444
+ import NewsletterSignup from '~/components/NewsletterSignup.astro';
445
+ import InlineTeamMember from '~/components/InlineTeamMember.astro';
446
+ ---
447
+
448
+ <div data-datocms-content-link-group>
449
+ <StructuredText
450
+ data={page.content}
451
+ blockComponents={{
452
+ CtaRecord: Cta,
453
+ }}
454
+ inlineBlockComponents={{
455
+ NewsletterSignupRecord: NewsletterSignup,
456
+ }}
457
+ inlineRecordComponents={{
458
+ TeamMemberRecord: InlineTeamMember,
459
+ }}
460
+ />
461
+ </div>
278
462
  ```
279
463
 
280
- This is useful for structural elements like `<video>`, `<audio>`, or `<iframe>` where stega encoding in visible text would be problematic.
464
+ With this setup:
465
+
466
+ - Clicking the main text (paragraphs, headings, lists) opens the **structured text field editor**
467
+ - Clicking an embedded block, inline block, or inline record opens **that block/record's editor**
281
468
 
282
469
  ## Low-level utilities
283
470
 
@@ -370,15 +557,17 @@ If navigation isn't syncing between your preview and the DatoCMS interface:
370
557
  If structured text content isn't opening the editor:
371
558
 
372
559
  1. **Wrap with `data-datocms-content-link-group`:**
560
+ See [Rule 1: Always wrap the Structured Text component in a group](#rule-1-always-wrap-the-structured-text-component-in-a-group).
373
561
 
374
- ```astro
375
- <div data-datocms-content-link-group>
376
- <StructuredText data={content.body} />
377
- </div>
378
- ```
562
+ 2. **Add boundaries for embedded blocks and inline records:**
563
+ See [Rule 2: Wrap embedded blocks and inline records in a boundary](#rule-2-wrap-embedded-blocks-and-inline-records-in-a-boundary).
379
564
 
380
- 2. **Check for `data-datocms-content-link-boundary` blocking clicks:**
565
+ 3. **Check for `data-datocms-content-link-boundary` blocking clicks:**
381
566
  Make sure you haven't accidentally added a boundary attribute that's preventing the click from reaching the group.
382
567
 
383
- 3. **Verify stega encoding is present:**
568
+ 4. **Verify stega encoding is present:**
384
569
  Use the browser inspector to check if the structured text HTML contains zero-width characters (stega encoding). If not, check your query options.
570
+
571
+ ### Layout issues caused by stega encoding
572
+
573
+ The invisible zero-width characters can cause unexpected letter-spacing or text breaking out of containers. To fix this, either use `stripStega: true`, or use CSS: `[data-datocms-contains-stega] { letter-spacing: 0 !important; }`. This attribute is automatically added to elements with stega-encoded content when `stripStega: false` (the default). See [`data-datocms-contains-stega`](#data-datocms-contains-stega) for more details.