@bettercms-ai/astro 0.8.2 → 0.9.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.
- package/components/BcmsBlocks.astro +162 -45
- package/components/BcmsForm.astro +7 -1
- package/package.json +4 -4
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
* actually contains that block (mirrors render-page.ts's `ctx.flags` gating).
|
|
27
27
|
*/
|
|
28
28
|
import type { ContentBlock } from "@bettercms-ai/types";
|
|
29
|
-
import { blockStyleToCss } from "@bettercms-ai/types";
|
|
29
|
+
import { blockStyleToCss, componentInstanceAddresses } from "@bettercms-ai/types";
|
|
30
|
+
import { bind, bindProps } from "../src/bindings.js";
|
|
30
31
|
import type { DeliveryComponent, DeliveryForm } from "@bettercms-ai/sdk";
|
|
31
32
|
import { applyOverrides } from "@bettercms-ai/ui/resolve";
|
|
32
33
|
import BcmsImage from "./BcmsImage.astro";
|
|
@@ -46,6 +47,15 @@ interface Props {
|
|
|
46
47
|
/** Set on every self-recursion so only the outermost instance emits the runtime
|
|
47
48
|
* scripts. Internal; do not pass. */
|
|
48
49
|
nested?: boolean;
|
|
50
|
+
/** Attributes for the container <div> this component always emits. Used only by the
|
|
51
|
+
* `component` case, so a component instance's marker rides the container its own
|
|
52
|
+
* recursion already produces instead of gaining a second <div>. Internal; do not pass. */
|
|
53
|
+
containerAttrs?: Record<string, string | undefined>;
|
|
54
|
+
/** Inside a component instance, the DEFINITION's `<blockId>__<prop>` mapped to this
|
|
55
|
+
* instance's `<id>__overrides.<key>`. AUTHORITATIVE where present: a prop the definition
|
|
56
|
+
* does not declare gets no address, rather than one that binds to no field.
|
|
57
|
+
* Internal; do not pass. */
|
|
58
|
+
fieldAddresses?: Map<string, string>;
|
|
49
59
|
class?: string;
|
|
50
60
|
}
|
|
51
61
|
|
|
@@ -60,8 +70,12 @@ const {
|
|
|
60
70
|
depth = 0,
|
|
61
71
|
seen = [],
|
|
62
72
|
nested = false,
|
|
73
|
+
containerAttrs = {},
|
|
74
|
+
fieldAddresses,
|
|
63
75
|
class: className,
|
|
64
76
|
} = Astro.props;
|
|
77
|
+
/** What `bind`/`bindProps` read. One object, so every call site cannot disagree. */
|
|
78
|
+
const bctx = { fieldAddresses };
|
|
65
79
|
const formMap = new Map(forms.map((f) => [f.id, f]));
|
|
66
80
|
const componentMap = new Map(components.map((c) => [c.id, c]));
|
|
67
81
|
|
|
@@ -91,7 +105,9 @@ function styleAttr(block: ContentBlock): string | undefined {
|
|
|
91
105
|
* template expression as JSX — `applyOverrides<ContentBlock>(…)` inline emitted a bogus
|
|
92
106
|
* `<ContentBlock>` element and the file failed to compile at all.
|
|
93
107
|
*/
|
|
94
|
-
function resolveComponent(
|
|
108
|
+
function resolveComponent(
|
|
109
|
+
p: Record<string, any>,
|
|
110
|
+
): { id: string; blocks: ContentBlock[]; props: DeliveryComponent["props"] } | null {
|
|
95
111
|
const def = componentMap.get(String(p.componentId ?? ""));
|
|
96
112
|
if (!def) return null;
|
|
97
113
|
if (depth >= MAX_COMPONENT_DEPTH || seen.includes(def.id)) return null;
|
|
@@ -100,7 +116,9 @@ function resolveComponent(p: Record<string, any>): { id: string; blocks: Content
|
|
|
100
116
|
def.props ?? [],
|
|
101
117
|
(p.overrides as Record<string, unknown>) ?? {},
|
|
102
118
|
);
|
|
103
|
-
|
|
119
|
+
// `props` comes back too: the caller needs the DECLARATION (which block each prop drives) to
|
|
120
|
+
// build this instance's addresses, not just the resolved tree.
|
|
121
|
+
return { id: def.id, blocks: resolved, props: def.props ?? [] };
|
|
104
122
|
}
|
|
105
123
|
|
|
106
124
|
/**
|
|
@@ -134,7 +152,10 @@ const needsSlider = !nested && containsType(blocks, "slider");
|
|
|
134
152
|
const needsTabs = !nested && containsType(blocks, "tabs");
|
|
135
153
|
|
|
136
154
|
/** Props every self-recursion forwards unchanged. */
|
|
137
|
-
|
|
155
|
+
// `fieldAddresses` rides along: a slide or tab panel is still inside the SAME component
|
|
156
|
+
// instance, so its children keep that instance's addresses. The `component` case below
|
|
157
|
+
// overrides it with the NEW instance's map.
|
|
158
|
+
const pass = { forms, components, turnstileSiteKey, nested: true, fieldAddresses };
|
|
138
159
|
---
|
|
139
160
|
|
|
140
161
|
{/*
|
|
@@ -150,46 +171,96 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
150
171
|
component recurses into itself for columns/tabs/sliders/components, so an HTML comment here
|
|
151
172
|
shipped once per nesting level to every visitor (FLO-1161).
|
|
152
173
|
*/}
|
|
153
|
-
<div class={className} data-bcms-page={nested ? undefined : ""}>
|
|
174
|
+
<div class={className} data-bcms-page={nested ? undefined : ""} {...containerAttrs}>
|
|
154
175
|
{blocks.map((block) => {
|
|
155
176
|
const p = anyProps(block);
|
|
156
177
|
const style = styleAttr(block);
|
|
178
|
+
/**
|
|
179
|
+
* 🔴 `data-bcms-block` — the Visual Editor hit-tests this to find sections. It rides the
|
|
180
|
+
* block's OWN root element; it does NOT get a wrapper.
|
|
181
|
+
*
|
|
182
|
+
* A wrapper <div> is what used to be emitted here, and it was a block box around every
|
|
183
|
+
* top-level block. Two consecutive `button` blocks — whose `<a class="bcms-btn">` roots are
|
|
184
|
+
* `inline-flex` and are meant to sit side by side — were each sealed in their own block box
|
|
185
|
+
* and stacked. Measured on the live amor site: both wrappers `display:block`, width 1048,
|
|
186
|
+
* y=462 and y=540. The Next adapter never had this because React lets it `cloneElement`;
|
|
187
|
+
* Astro has no cloneElement, so the attribute is applied AT CONSTRUCTION instead — the same
|
|
188
|
+
* spread idiom this file already uses for the image binding below.
|
|
189
|
+
*
|
|
190
|
+
* `undefined` values are omitted by Astro, which is why `nested` blocks (an empty object
|
|
191
|
+
* here) emit nothing — a column's child must never look like a draggable section.
|
|
192
|
+
*
|
|
193
|
+
* When the block carries style tokens the wrapper below is its root, so the marker rides
|
|
194
|
+
* that instead and `bind` stays empty. Style tokens keep their own box: merging them onto
|
|
195
|
+
* the block's root changes behaviour (a spacer's padding is absorbed under `border-box`, a
|
|
196
|
+
* section's `padding` collides last-wins instead of adding, and a button's `text-align`
|
|
197
|
+
* would centre its label instead of the button).
|
|
198
|
+
*/
|
|
199
|
+
const blockAttrs = nested || style ? {} : { "data-bcms-block": block.id };
|
|
157
200
|
const node = (() => {
|
|
158
201
|
switch (block.type) {
|
|
159
202
|
case "heading": {
|
|
160
203
|
const Tag = `h${clamp(Number(p.level) || 2, 1, 6)}` as keyof HTMLElementTagNameMap;
|
|
161
|
-
return <Tag
|
|
204
|
+
return <Tag {...bind(block.id, "text", "text", bctx)} {...blockAttrs}>{p.text}</Tag>;
|
|
162
205
|
}
|
|
163
206
|
// `richtext` and `text` carry the same payload and differ only in the authoring
|
|
164
207
|
// surface, so delivery renders them identically — the hosted renderer does the same.
|
|
165
208
|
case "text":
|
|
166
209
|
case "richtext":
|
|
167
|
-
return <div class="bcms-text"
|
|
210
|
+
return <div class="bcms-text" {...bind(block.id, "html", "richtext", bctx)} {...blockAttrs} set:html={p.html ?? ""} />;
|
|
168
211
|
case "image": {
|
|
169
212
|
// Matches the Next adapter: the image block is click-to-select on the Visual Editor
|
|
170
213
|
// canvas like heading/text/button, rather than dock-only. `kind="image"` means select
|
|
171
214
|
// + live src reflection, never contentEditable.
|
|
172
|
-
|
|
215
|
+
// `alt` rides the props lane: this element already carries the src binding and one
|
|
216
|
+
// element cannot hold two addresses.
|
|
217
|
+
const imgBind = {
|
|
218
|
+
...bind(block.id, "src", "image", bctx),
|
|
219
|
+
...bindProps(block.id, [{ prop: "alt", kind: "text", attr: "alt", present: p.alt != null }], bctx),
|
|
220
|
+
};
|
|
173
221
|
return p.caption ? (
|
|
174
|
-
<figure>
|
|
175
|
-
<BcmsImage src={p.src} alt={p.alt ?? ""} width={p.width} height={p.height} {...
|
|
176
|
-
<figcaption>{p.caption}</figcaption>
|
|
222
|
+
<figure {...blockAttrs}>
|
|
223
|
+
<BcmsImage src={p.src} alt={p.alt ?? ""} width={p.width} height={p.height} {...imgBind} />
|
|
224
|
+
<figcaption {...bind(block.id, "caption", "text", bctx)}>{p.caption}</figcaption>
|
|
177
225
|
</figure>
|
|
178
226
|
) : (
|
|
179
|
-
<BcmsImage src={p.src} alt={p.alt ?? ""} width={p.width} height={p.height} {...
|
|
227
|
+
<BcmsImage src={p.src} alt={p.alt ?? ""} width={p.width} height={p.height} {...imgBind} {...blockAttrs} />
|
|
180
228
|
);
|
|
181
229
|
}
|
|
182
230
|
case "button":
|
|
183
231
|
return (
|
|
184
|
-
<a
|
|
232
|
+
<a
|
|
233
|
+
class={`bcms-btn bcms-btn-${p.variant === "secondary" ? "secondary" : "primary"}`}
|
|
234
|
+
href={p.href ?? "#"}
|
|
235
|
+
{...bind(block.id, "text", "text", bctx)}
|
|
236
|
+
{...bindProps(block.id, [{ prop: "href", kind: "url", attr: "href", present: p.href != null }], bctx)}
|
|
237
|
+
{...blockAttrs}
|
|
238
|
+
>
|
|
185
239
|
{p.text}
|
|
186
240
|
</a>
|
|
187
241
|
);
|
|
188
242
|
case "spacer":
|
|
189
|
-
return <div class="bcms-spacer" style={`height:${Number(p.height) || 0}px`} />;
|
|
243
|
+
return <div class="bcms-spacer" style={`height:${Number(p.height) || 0}px`} {...blockAttrs} />;
|
|
190
244
|
case "video":
|
|
191
245
|
return (
|
|
192
|
-
<video
|
|
246
|
+
<video
|
|
247
|
+
src={p.url}
|
|
248
|
+
poster={p.poster}
|
|
249
|
+
controls
|
|
250
|
+
autoplay={!!p.autoplay}
|
|
251
|
+
muted={!!p.autoplay}
|
|
252
|
+
loop={!!p.loop}
|
|
253
|
+
playsinline
|
|
254
|
+
{...bindProps(
|
|
255
|
+
block.id,
|
|
256
|
+
[
|
|
257
|
+
{ prop: "url", kind: "url", attr: "src", present: p.url != null },
|
|
258
|
+
{ prop: "poster", kind: "url", attr: "poster", present: p.poster != null },
|
|
259
|
+
],
|
|
260
|
+
bctx,
|
|
261
|
+
)}
|
|
262
|
+
{...blockAttrs}
|
|
263
|
+
/>
|
|
193
264
|
);
|
|
194
265
|
case "columns": {
|
|
195
266
|
const cols: ContentBlock[][] = Array.isArray(p.columns) ? p.columns : [];
|
|
@@ -197,6 +268,7 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
197
268
|
<div
|
|
198
269
|
class="bcms-cols"
|
|
199
270
|
style={`display:grid;grid-template-columns:repeat(${cols.length || 1},1fr);gap:${Number(p.gap) || 0}px`}
|
|
271
|
+
{...blockAttrs}
|
|
200
272
|
>
|
|
201
273
|
{cols.map((col) => (
|
|
202
274
|
<div class="bcms-col">
|
|
@@ -208,7 +280,7 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
208
280
|
}
|
|
209
281
|
case "form": {
|
|
210
282
|
const form = formMap.get(p.formId);
|
|
211
|
-
return form ? <BcmsForm form={form} turnstileSiteKey={turnstileSiteKey} /> : null;
|
|
283
|
+
return form ? <BcmsForm form={form} turnstileSiteKey={turnstileSiteKey} {...blockAttrs} /> : null;
|
|
212
284
|
}
|
|
213
285
|
case "section": {
|
|
214
286
|
const inner = <Astro.self blocks={arr(p.children)} depth={depth} seen={seen} {...pass} />;
|
|
@@ -220,6 +292,7 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
220
292
|
p.background ? `background:${p.background}` : "",
|
|
221
293
|
p.paddingY != null ? `padding:${Number(p.paddingY) || 0}px 0` : "",
|
|
222
294
|
].filter(Boolean).join(";") || undefined}
|
|
295
|
+
{...blockAttrs}
|
|
223
296
|
>
|
|
224
297
|
{mw ? <div class="bcms-section-inner" style={`max-width:${mw}px;margin:0 auto`}>{inner}</div> : inner}
|
|
225
298
|
</section>
|
|
@@ -228,33 +301,68 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
228
301
|
case "navbar": {
|
|
229
302
|
const logo = p.logo && typeof p.logo === "object" ? p.logo : null;
|
|
230
303
|
return (
|
|
231
|
-
<nav class="bcms-navbar">
|
|
304
|
+
<nav class="bcms-navbar" {...blockAttrs}>
|
|
232
305
|
{logo?.src ? (
|
|
233
306
|
<a class="bcms-navbar-logo" href={logo.href ?? "/"}>
|
|
234
|
-
<img
|
|
307
|
+
<img
|
|
308
|
+
src={logo.src}
|
|
309
|
+
alt={logo.alt ?? ""}
|
|
310
|
+
{...bind(block.id, "logo.src", "image", bctx)}
|
|
311
|
+
{...bindProps(block.id, [{ prop: "logo.alt", kind: "text", attr: "alt", present: logo.alt != null }], bctx)}
|
|
312
|
+
/>
|
|
235
313
|
</a>
|
|
236
314
|
) : null}
|
|
237
315
|
<div class="bcms-navbar-links">
|
|
238
|
-
{
|
|
316
|
+
{/* Each link addressed by index. The old "nested under arrays" exclusion was
|
|
317
|
+
about a writer ceiling that no longer exists: setFieldPath walks N indices. */}
|
|
318
|
+
{arr(p.links).map((l: any, i: number) => (
|
|
319
|
+
<a
|
|
320
|
+
href={l?.href ?? "#"}
|
|
321
|
+
{...bind(block.id, `links[${i}].label`, "text", bctx)}
|
|
322
|
+
{...bindProps(block.id, [{ prop: `links[${i}].href`, kind: "url", attr: "href", present: l?.href != null }], bctx)}
|
|
323
|
+
>
|
|
324
|
+
{l?.label ?? ""}
|
|
325
|
+
</a>
|
|
326
|
+
))}
|
|
239
327
|
</div>
|
|
240
|
-
{p.cta?.text ?
|
|
328
|
+
{p.cta?.text ? (
|
|
329
|
+
<a
|
|
330
|
+
class="bcms-btn bcms-btn-primary"
|
|
331
|
+
href={p.cta.href ?? "#"}
|
|
332
|
+
{...bind(block.id, "cta.text", "text", bctx)}
|
|
333
|
+
{...bindProps(block.id, [{ prop: "cta.href", kind: "url", attr: "href", present: p.cta.href != null }], bctx)}
|
|
334
|
+
>
|
|
335
|
+
{p.cta.text}
|
|
336
|
+
</a>
|
|
337
|
+
) : null}
|
|
241
338
|
</nav>
|
|
242
339
|
);
|
|
243
340
|
}
|
|
244
341
|
case "footer":
|
|
245
342
|
return (
|
|
246
|
-
<footer class="bcms-footer">
|
|
343
|
+
<footer class="bcms-footer" {...blockAttrs}>
|
|
247
344
|
<div class="bcms-footer-cols">
|
|
248
|
-
{arr(p.columns).map((col: any) => (
|
|
345
|
+
{arr(p.columns).map((col: any, i: number) => (
|
|
249
346
|
<div class="bcms-footer-col">
|
|
250
|
-
{col?.heading ? <h4>{col.heading}</h4> : null}
|
|
347
|
+
{col?.heading ? <h4 {...bind(block.id, `columns[${i}].heading`, "text", bctx)}>{col.heading}</h4> : null}
|
|
251
348
|
<ul>
|
|
252
|
-
{
|
|
349
|
+
{/* Two indices deep — the deepest addressable thing on a page. */}
|
|
350
|
+
{arr(col?.links).map((l: any, j: number) => (
|
|
351
|
+
<li>
|
|
352
|
+
<a
|
|
353
|
+
href={l?.href ?? "#"}
|
|
354
|
+
{...bind(block.id, `columns[${i}].links[${j}].label`, "text", bctx)}
|
|
355
|
+
{...bindProps(block.id, [{ prop: `columns[${i}].links[${j}].href`, kind: "url", attr: "href", present: l?.href != null }], bctx)}
|
|
356
|
+
>
|
|
357
|
+
{l?.label ?? ""}
|
|
358
|
+
</a>
|
|
359
|
+
</li>
|
|
360
|
+
))}
|
|
253
361
|
</ul>
|
|
254
362
|
</div>
|
|
255
363
|
))}
|
|
256
364
|
</div>
|
|
257
|
-
{p.copyright ? <p class="bcms-footer-copy">{p.copyright}</p> : null}
|
|
365
|
+
{p.copyright ? <p class="bcms-footer-copy" {...bind(block.id, "copyright", "text", bctx)}>{p.copyright}</p> : null}
|
|
258
366
|
</footer>
|
|
259
367
|
);
|
|
260
368
|
case "slider":
|
|
@@ -268,6 +376,7 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
268
376
|
data-bcms-slider
|
|
269
377
|
data-autoplay={p.autoplay ? String(Number(p.interval) || 5000) : undefined}
|
|
270
378
|
data-loop={p.loop ? "" : undefined}
|
|
379
|
+
{...blockAttrs}
|
|
271
380
|
>
|
|
272
381
|
<div class="bcms-slides">
|
|
273
382
|
{arr(p.slides).map((s: any, i: number) => (
|
|
@@ -282,10 +391,16 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
282
391
|
);
|
|
283
392
|
case "tabs":
|
|
284
393
|
return (
|
|
285
|
-
<div class="bcms-tabs" data-bcms-tabs>
|
|
394
|
+
<div class="bcms-tabs" data-bcms-tabs {...blockAttrs}>
|
|
286
395
|
<div class="bcms-tablist" role="tablist">
|
|
287
396
|
{arr(p.tabs).map((t: any, i: number) => (
|
|
288
|
-
<button
|
|
397
|
+
<button
|
|
398
|
+
role="tab"
|
|
399
|
+
class="bcms-tab"
|
|
400
|
+
aria-selected={i === 0 ? "true" : "false"}
|
|
401
|
+
data-tab={String(i)}
|
|
402
|
+
{...bind(block.id, `tabs[${i}].label`, "text", bctx)}
|
|
403
|
+
>
|
|
289
404
|
{t?.label ?? ""}
|
|
290
405
|
</button>
|
|
291
406
|
))}
|
|
@@ -300,7 +415,20 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
300
415
|
case "component": {
|
|
301
416
|
const resolved = resolveComponent(p);
|
|
302
417
|
if (!resolved) return null;
|
|
303
|
-
|
|
418
|
+
// 🔴 `fieldAddresses` AFTER `{...pass}`: the instance's own map must replace the
|
|
419
|
+
// enclosing one, not inherit it. Every stamp inside this subtree otherwise names a
|
|
420
|
+
// block belonging to the DEFINITION — shared by every instance of it, so it resolves
|
|
421
|
+
// to no field on one placement and collides into a dropped duplicate on two.
|
|
422
|
+
return (
|
|
423
|
+
<Astro.self
|
|
424
|
+
blocks={resolved.blocks}
|
|
425
|
+
depth={depth + 1}
|
|
426
|
+
seen={[...seen, resolved.id]}
|
|
427
|
+
containerAttrs={blockAttrs}
|
|
428
|
+
{...pass}
|
|
429
|
+
fieldAddresses={componentInstanceAddresses(block.id, resolved.props)}
|
|
430
|
+
/>
|
|
431
|
+
);
|
|
304
432
|
}
|
|
305
433
|
default:
|
|
306
434
|
return null;
|
|
@@ -310,25 +438,14 @@ const pass = { forms, components, turnstileSiteKey, nested: true };
|
|
|
310
438
|
// the builder canvas, the Next adapter and the hosted site.
|
|
311
439
|
if (node == null) return null;
|
|
312
440
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
* TOP LEVEL ONLY (`!nested`): a section IS a top-level block, and tagging nested ones would
|
|
319
|
-
* make a column's child look like a section you could drag out of its parent.
|
|
320
|
-
*
|
|
321
|
-
* A wrapper element rather than an attribute on `node`, because Astro renders to HTML and has
|
|
322
|
-
* no `cloneElement` — the same constraint that made the hosted renderer wrap
|
|
323
|
-
* (`render-page.ts`), and the same reasoning applies: a plain block-level div, so a full-bleed
|
|
324
|
-
* section background still reaches the edges. The Next adapter clones instead, precisely
|
|
325
|
-
* because React lets it and a wrapper there would land inside the customer's own layout.
|
|
441
|
+
* The style wrapper is the ONLY wrapper left, and it is unchanged: a style token needs a box
|
|
442
|
+
* to apply to. When it exists it IS this block's root, so it carries the marker and
|
|
443
|
+
* `blockAttrs` above is empty — the two are mutually exclusive by construction, so a block
|
|
444
|
+
* never gets the attribute twice and never gets a second <div>.
|
|
326
445
|
*
|
|
327
|
-
*
|
|
446
|
+
* Everything else now carries `data-bcms-block` on its own root (see `blockAttrs` above).
|
|
328
447
|
*/
|
|
329
|
-
|
|
330
|
-
if (style) return <div style={style} data-bcms-block={blockId}>{node}</div>;
|
|
331
|
-
if (blockId) return <div data-bcms-block={blockId}>{node}</div>;
|
|
448
|
+
if (style) return <div style={style} data-bcms-block={nested ? undefined : block.id}>{node}</div>;
|
|
332
449
|
return node;
|
|
333
450
|
})}
|
|
334
451
|
</div>
|
|
@@ -14,9 +14,14 @@ interface Props {
|
|
|
14
14
|
form: DeliveryForm;
|
|
15
15
|
turnstileSiteKey?: string | null;
|
|
16
16
|
class?: string;
|
|
17
|
+
/** Anything else lands on the <form> itself. <BcmsBlocks> uses this to put a form block's
|
|
18
|
+
* `data-bcms-block` marker on its own root — without it the attribute is passed as a prop
|
|
19
|
+
* nothing reads, the marker silently vanishes, and a top-level form becomes a section the
|
|
20
|
+
* Visual Editor cannot see. Caught by the marker canary in blocks.test.ts. */
|
|
21
|
+
[key: `data-${string}`]: unknown;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
const { form, turnstileSiteKey, class: className } = Astro.props;
|
|
24
|
+
const { form, turnstileSiteKey, class: className, ...rest } = Astro.props;
|
|
20
25
|
const action = `${config.apiUrl}/api/v1/forms/public/${encodeURIComponent(form.id)}/submissions`;
|
|
21
26
|
|
|
22
27
|
const INPUT_TYPE: Partial<Record<DeliveryFormField["type"], string>> = {
|
|
@@ -37,6 +42,7 @@ const showTurnstile = Boolean(form.turnstileEnabled && turnstileSiteKey);
|
|
|
37
42
|
data-bcms-form={form.id}
|
|
38
43
|
data-bcms-form-success={form.successMessage ?? "Thanks!"}
|
|
39
44
|
data-bcms-form-redirect={form.redirectUrl ?? ""}
|
|
45
|
+
{...rest}
|
|
40
46
|
>
|
|
41
47
|
{form.fields?.map((f) => {
|
|
42
48
|
const wrapAttrs = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bettercms-ai/astro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The BetterCMS adapter for Astro — a `bettercms()` integration, a `bettercms:client` virtual module, typed content loaders, draft preview, and native .astro rendering components.",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@bettercms-ai/image-url": "^0.2.0",
|
|
51
|
-
"@bettercms-ai/sdk": "^1.
|
|
52
|
-
"@bettercms-ai/types": "^1.
|
|
53
|
-
"@bettercms-ai/ui": "^0.
|
|
51
|
+
"@bettercms-ai/sdk": "^1.13.0",
|
|
52
|
+
"@bettercms-ai/types": "^1.11.0",
|
|
53
|
+
"@bettercms-ai/ui": "^0.6.0"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"astro": ">=4"
|