@batterai/block-sdk 0.1.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/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +22 -0
- package/dist/base-element.d.ts +37 -0
- package/dist/base-element.d.ts.map +1 -0
- package/dist/base-element.js +46 -0
- package/dist/base-element.js.map +1 -0
- package/dist/blocks/callout.css +1 -0
- package/dist/blocks/callout.d.ts +13 -0
- package/dist/blocks/callout.d.ts.map +1 -0
- package/dist/blocks/callout.js +106 -0
- package/dist/blocks/callout.js.map +1 -0
- package/dist/blocks/first-party.d.ts +3 -0
- package/dist/blocks/first-party.d.ts.map +1 -0
- package/dist/blocks/first-party.js +7 -0
- package/dist/blocks/first-party.js.map +1 -0
- package/dist/blocks/form.css +1 -0
- package/dist/blocks/form.d.ts +67 -0
- package/dist/blocks/form.d.ts.map +1 -0
- package/dist/blocks/form.js +199 -0
- package/dist/blocks/form.js.map +1 -0
- package/dist/blocks/image.css +1 -0
- package/dist/blocks/image.d.ts +49 -0
- package/dist/blocks/image.d.ts.map +1 -0
- package/dist/blocks/image.js +238 -0
- package/dist/blocks/image.js.map +1 -0
- package/dist/define.d.ts +9 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +12 -0
- package/dist/define.js.map +1 -0
- package/dist/html-package.d.ts +16 -0
- package/dist/html-package.d.ts.map +1 -0
- package/dist/html-package.js +104 -0
- package/dist/html-package.js.map +1 -0
- package/dist/i18n.d.ts +88 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +93 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/render-to-dsd.d.ts +18 -0
- package/dist/render-to-dsd.d.ts.map +1 -0
- package/dist/render-to-dsd.js +72 -0
- package/dist/render-to-dsd.js.map +1 -0
- package/dist/tag.d.ts +14 -0
- package/dist/tag.d.ts.map +1 -0
- package/dist/tag.js +25 -0
- package/dist/tag.js.map +1 -0
- package/package.json +43 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Batterai.eu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @batterai/block-sdk
|
|
2
|
+
|
|
3
|
+
Framework-neutral block registration and server-rendering helpers for Battery blocks.
|
|
4
|
+
|
|
5
|
+
## What It Provides
|
|
6
|
+
|
|
7
|
+
- `defineBlock` and tag helpers for block packages.
|
|
8
|
+
- A Lit-based base element for browser rendering.
|
|
9
|
+
- Declarative Shadow DOM server rendering utilities.
|
|
10
|
+
- Built-in callout, form, and image block examples.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @batterai/block-sdk
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Requires Node.js `>=20.11`.
|
|
19
|
+
|
|
20
|
+
## Boundary
|
|
21
|
+
|
|
22
|
+
Blocks receive resolved props and host-supplied data. They do not own page persistence; the host CMS remains the durable source of truth.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BatteryBlockElement — the Lit base class for block elements.
|
|
3
|
+
* - pure function of the `config` PROPERTY (host-assigned; contract §3)
|
|
4
|
+
* - DSD adoption: with `@lit-labs/ssr-client`'s hydrate support loaded FIRST
|
|
5
|
+
* (invariant A), an existing server shadow root is adopted, never re-rendered
|
|
6
|
+
* - emits be-ready on connect; helpers for be-edit / be-intent
|
|
7
|
+
* - editingRegion contract (plan §5.1): subclasses call `suppressedRegion()`
|
|
8
|
+
* to render an identical-box placeholder while that region is overlay-edited
|
|
9
|
+
*/
|
|
10
|
+
import { LitElement } from "lit";
|
|
11
|
+
import { type BeEditDetail, type BeIntentDetail, type BlockConfig } from "@batterai/contract";
|
|
12
|
+
export declare class BatteryBlockElement extends LitElement {
|
|
13
|
+
/** `config` is BOTH a property (host mount path) and a JSON attribute (the
|
|
14
|
+
* SSR path): HTML cannot serialize JS properties into DSD, so the server
|
|
15
|
+
* binds the attribute and the upgraded client element parses it — giving
|
|
16
|
+
* hydration the IDENTICAL data the server rendered with (invariant A;
|
|
17
|
+
* spike-proven; the research memo's "DSD property-binding gap"). */
|
|
18
|
+
static properties: {
|
|
19
|
+
config: {
|
|
20
|
+
attribute: string;
|
|
21
|
+
type: ObjectConstructor;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
config: BlockConfig | null;
|
|
25
|
+
/** Set by defineBlock() so be-ready carries identity without subclass code. */
|
|
26
|
+
static blockType: string;
|
|
27
|
+
static blockVersion: string;
|
|
28
|
+
constructor();
|
|
29
|
+
connectedCallback(): void;
|
|
30
|
+
protected emitEdit(detail: BeEditDetail): void;
|
|
31
|
+
protected emitIntent(detail: BeIntentDetail): void;
|
|
32
|
+
/** True when `regionId` must render as a stable-box placeholder because the
|
|
33
|
+
* host's overlay is editing it (editMode only; renderToDSD paths never see
|
|
34
|
+
* an editingRegion — invariant L). */
|
|
35
|
+
protected suppressedRegion(regionId: string): boolean;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=base-element.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-element.d.ts","sourceRoot":"","sources":["../src/base-element.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AACjC,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,WAAW,EACjB,MAAM,oBAAoB,CAAC;AAE5B,qBAAa,mBAAoB,SAAQ,UAAU;IACjD;;;;wEAIoE;IACpE,MAAM,CAAC,UAAU;;;;;MAEf;IAEM,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAEnC,+EAA+E;IAC/E,MAAM,CAAC,SAAS,SAAM;IACtB,MAAM,CAAC,YAAY,SAAM;;IAOhB,iBAAiB,IAAI,IAAI;IAMlC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAI9C,SAAS,CAAC,UAAU,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI;IAIlD;;0CAEsC;IACtC,SAAS,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;CAGtD"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BatteryBlockElement — the Lit base class for block elements.
|
|
3
|
+
* - pure function of the `config` PROPERTY (host-assigned; contract §3)
|
|
4
|
+
* - DSD adoption: with `@lit-labs/ssr-client`'s hydrate support loaded FIRST
|
|
5
|
+
* (invariant A), an existing server shadow root is adopted, never re-rendered
|
|
6
|
+
* - emits be-ready on connect; helpers for be-edit / be-intent
|
|
7
|
+
* - editingRegion contract (plan §5.1): subclasses call `suppressedRegion()`
|
|
8
|
+
* to render an identical-box placeholder while that region is overlay-edited
|
|
9
|
+
*/
|
|
10
|
+
import { LitElement } from "lit";
|
|
11
|
+
import { BE_READY, emitBlockEvent, } from "@batterai/contract";
|
|
12
|
+
export class BatteryBlockElement extends LitElement {
|
|
13
|
+
/** `config` is BOTH a property (host mount path) and a JSON attribute (the
|
|
14
|
+
* SSR path): HTML cannot serialize JS properties into DSD, so the server
|
|
15
|
+
* binds the attribute and the upgraded client element parses it — giving
|
|
16
|
+
* hydration the IDENTICAL data the server rendered with (invariant A;
|
|
17
|
+
* spike-proven; the research memo's "DSD property-binding gap"). */
|
|
18
|
+
static properties = {
|
|
19
|
+
config: { attribute: "config", type: Object },
|
|
20
|
+
};
|
|
21
|
+
/** Set by defineBlock() so be-ready carries identity without subclass code. */
|
|
22
|
+
static blockType = "";
|
|
23
|
+
static blockVersion = "";
|
|
24
|
+
constructor() {
|
|
25
|
+
super();
|
|
26
|
+
this.config = null;
|
|
27
|
+
}
|
|
28
|
+
connectedCallback() {
|
|
29
|
+
super.connectedCallback();
|
|
30
|
+
const ctor = this.constructor;
|
|
31
|
+
emitBlockEvent(this, BE_READY, { type: ctor.blockType, version: ctor.blockVersion });
|
|
32
|
+
}
|
|
33
|
+
emitEdit(detail) {
|
|
34
|
+
emitBlockEvent(this, "be-edit", detail);
|
|
35
|
+
}
|
|
36
|
+
emitIntent(detail) {
|
|
37
|
+
emitBlockEvent(this, "be-intent", detail);
|
|
38
|
+
}
|
|
39
|
+
/** True when `regionId` must render as a stable-box placeholder because the
|
|
40
|
+
* host's overlay is editing it (editMode only; renderToDSD paths never see
|
|
41
|
+
* an editingRegion — invariant L). */
|
|
42
|
+
suppressedRegion(regionId) {
|
|
43
|
+
return this.config?.context.editMode === true && this.config.context.editingRegion === regionId;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=base-element.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-element.js","sourceRoot":"","sources":["../src/base-element.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AACjC,OAAO,EACL,QAAQ,EACR,cAAc,GAIf,MAAM,oBAAoB,CAAC;AAE5B,MAAM,OAAO,mBAAoB,SAAQ,UAAU;IACjD;;;;wEAIoE;IACpE,MAAM,CAAC,UAAU,GAAG;QAClB,MAAM,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE;KAC9C,CAAC;IAIF,+EAA+E;IAC/E,MAAM,CAAC,SAAS,GAAG,EAAE,CAAC;IACtB,MAAM,CAAC,YAAY,GAAG,EAAE,CAAC;IAEzB;QACE,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IACrB,CAAC;IAEQ,iBAAiB;QACxB,KAAK,CAAC,iBAAiB,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,WAAyC,CAAC;QAC5D,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;IACvF,CAAC;IAES,QAAQ,CAAC,MAAoB;QACrC,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IAES,UAAU,CAAC,MAAsB;QACzC,cAAc,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IAED;;0CAEsC;IAC5B,gBAAgB,CAAC,QAAgB;QACzC,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,aAAa,KAAK,QAAQ,CAAC;IAClG,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
be-battery-callout-v1 { display: block; }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Locale } from "@batterai/contract";
|
|
2
|
+
import type { BlockManifest, BlockPackage } from "@batterai/contract";
|
|
3
|
+
import { BatteryBlockElement } from "../base-element";
|
|
4
|
+
export declare function createCalloutManifest(locale?: Locale | string): BlockManifest;
|
|
5
|
+
export declare const calloutManifest: BlockManifest;
|
|
6
|
+
export declare class BatteryCallout extends BatteryBlockElement {
|
|
7
|
+
static styles: import("lit").CSSResult;
|
|
8
|
+
private regionClick;
|
|
9
|
+
render(): import("lit").TemplateResult<1>;
|
|
10
|
+
}
|
|
11
|
+
export declare function createCalloutPackage(locale?: Locale | string): BlockPackage;
|
|
12
|
+
export declare const calloutPackage: BlockPackage;
|
|
13
|
+
//# sourceMappingURL=callout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callout.d.ts","sourceRoot":"","sources":["../../src/blocks/callout.ts"],"names":[],"mappings":"AAOA,OAAO,EAAyB,KAAK,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAwBtD,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,CAoB7E;AAED,eAAO,MAAM,eAAe,EAAE,aAA2C,CAAC;AAE1E,qBAAa,cAAe,SAAQ,mBAAmB;IACrD,MAAM,CAAC,MAAM,0BASX;IAEF,OAAO,CAAC,WAAW;IAQV,MAAM;CAsBhB;AAED,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,YAAY,CAO3E;AAED,eAAO,MAAM,cAAc,EAAE,YAAyC,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-party proof block `battery.callout` (Tier 2, contract §10 example
|
|
3
|
+
* generalized): title + body with an accent bar; both regions inline-editable
|
|
4
|
+
* via be-intent{edit-region} in editMode; pure function of RESOLVED props.
|
|
5
|
+
* Used by the Phase-4 gate specs and every later phase's fixtures.
|
|
6
|
+
*/
|
|
7
|
+
import { css, html, nothing } from "lit";
|
|
8
|
+
import { fieldName } from "@batterai/contract";
|
|
9
|
+
import { MULTILINE_TEXT_COMPONENT } from "@batterai/contract";
|
|
10
|
+
import { BatteryBlockElement } from "../base-element.js";
|
|
11
|
+
import { defineBlock } from "../define.js";
|
|
12
|
+
import { blockSdkMessage } from "../i18n.js";
|
|
13
|
+
function fields(locale) {
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
name: fieldName("title"),
|
|
17
|
+
label: blockSdkMessage(locale, "callout.field.title.label"),
|
|
18
|
+
valueType: "string",
|
|
19
|
+
cardinality: "one",
|
|
20
|
+
bindable: true,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: fieldName("body"),
|
|
24
|
+
label: blockSdkMessage(locale, "callout.field.body.label"),
|
|
25
|
+
valueType: "string",
|
|
26
|
+
cardinality: "one",
|
|
27
|
+
bindable: true,
|
|
28
|
+
render: { component: MULTILINE_TEXT_COMPONENT },
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
export function createCalloutManifest(locale) {
|
|
33
|
+
return {
|
|
34
|
+
type: "battery.callout",
|
|
35
|
+
version: "1.0.0",
|
|
36
|
+
source: "battery",
|
|
37
|
+
label: blockSdkMessage(locale, "callout.manifest.label"),
|
|
38
|
+
summary: blockSdkMessage(locale, "callout.manifest.summary"),
|
|
39
|
+
category: "content",
|
|
40
|
+
icon: "text",
|
|
41
|
+
fields: fields(locale),
|
|
42
|
+
defaultProps: {
|
|
43
|
+
title: blockSdkMessage(locale, "callout.default.title"),
|
|
44
|
+
body: blockSdkMessage(locale, "callout.default.body"),
|
|
45
|
+
},
|
|
46
|
+
editableRegions: [
|
|
47
|
+
{ regionId: "title", field: "title", target: "[data-edit='title']", kind: "rich" },
|
|
48
|
+
{ regionId: "body", field: "body", target: "[data-edit='body']", kind: "rich", multiline: true },
|
|
49
|
+
],
|
|
50
|
+
capabilities: { ssr: true, hydration: "partial" },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export const calloutManifest = createCalloutManifest("en");
|
|
54
|
+
export class BatteryCallout extends BatteryBlockElement {
|
|
55
|
+
static styles = css `
|
|
56
|
+
.bar {
|
|
57
|
+
border-left: 4px solid var(--be-color-accent, rebeccapurple);
|
|
58
|
+
padding: 8px 16px;
|
|
59
|
+
font-family: system-ui, sans-serif;
|
|
60
|
+
}
|
|
61
|
+
h3 { margin: 0 0 4px; font-size: 20px; line-height: 1.3; }
|
|
62
|
+
p { margin: 0; font-size: 16px; line-height: 1.5; }
|
|
63
|
+
.be-region-placeholder { visibility: hidden; }
|
|
64
|
+
`;
|
|
65
|
+
regionClick(field, regionId, e) {
|
|
66
|
+
if (!this.config?.context.editMode)
|
|
67
|
+
return;
|
|
68
|
+
this.emitIntent({
|
|
69
|
+
action: "edit-region",
|
|
70
|
+
payload: { field, regionId, clientX: e.clientX, clientY: e.clientY },
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
render() {
|
|
74
|
+
const c = this.config;
|
|
75
|
+
if (!c)
|
|
76
|
+
return html `<p class="be-unconfigured">${blockSdkMessage(undefined, "block.unconfigured")}</p>`;
|
|
77
|
+
const title = typeof c.props.title === "string" ? c.props.title : "";
|
|
78
|
+
const body = typeof c.props.body === "string" ? c.props.body : "";
|
|
79
|
+
return html `<article class="bar">
|
|
80
|
+
<h3
|
|
81
|
+
data-edit="title"
|
|
82
|
+
class=${this.suppressedRegion("title") ? "be-region-placeholder" : nothing}
|
|
83
|
+
@click=${(e) => this.regionClick("title", "title", e)}
|
|
84
|
+
>
|
|
85
|
+
${title}
|
|
86
|
+
</h3>
|
|
87
|
+
<p
|
|
88
|
+
data-edit="body"
|
|
89
|
+
class=${this.suppressedRegion("body") ? "be-region-placeholder" : nothing}
|
|
90
|
+
@click=${(e) => this.regionClick("body", "body", e)}
|
|
91
|
+
>
|
|
92
|
+
${body}
|
|
93
|
+
</p>
|
|
94
|
+
</article>`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export function createCalloutPackage(locale) {
|
|
98
|
+
const manifest = createCalloutManifest(locale);
|
|
99
|
+
return {
|
|
100
|
+
manifest,
|
|
101
|
+
stylesheets: [new URL("./callout.css", import.meta.url).href],
|
|
102
|
+
defineElement: defineBlock(manifest, BatteryCallout),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export const calloutPackage = createCalloutPackage("en");
|
|
106
|
+
//# sourceMappingURL=callout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callout.js","sourceRoot":"","sources":["../../src/blocks/callout.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"first-party.d.ts","sourceRoot":"","sources":["../../src/blocks/first-party.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAKhE,wBAAgB,8BAA8B,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,aAAa,EAAE,CAEjG"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createCalloutManifest } from "./callout.js";
|
|
2
|
+
import { createFormManifest } from "./form.js";
|
|
3
|
+
import { createImageManifest } from "./image.js";
|
|
4
|
+
export function createFirstPartyBlockManifests(locale) {
|
|
5
|
+
return [createCalloutManifest(locale), createImageManifest(locale), createFormManifest(locale)];
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=first-party.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"first-party.js","sourceRoot":"","sources":["../../src/blocks/first-party.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,UAAU,8BAA8B,CAAC,MAAwB;IACrE,OAAO,CAAC,qBAAqB,CAAC,MAAM,CAAC,EAAE,mBAAmB,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC;AAClG,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
be-battery-form-v1 { display: block; }
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-party `battery.form` (Tier 3, hydration "partial") — the v1 form
|
|
3
|
+
* block posts to a host action endpoint, which routes through HostApi.mutate:
|
|
4
|
+
* - fields render from a literal `fieldsJson` spec (name/label/required/
|
|
5
|
+
* multiline) — SSR'd, so the form is usable at first paint;
|
|
6
|
+
* - the write TARGET is never wire-authored: the block posts only
|
|
7
|
+
* { formId, instanceId, idempotencyKey, values } to the host `action`
|
|
8
|
+
* endpoint; the host maps formId -> registered target server-side and
|
|
9
|
+
* runs checkWriteAuthz (tenant-scoped, invariant D order);
|
|
10
|
+
* - client-side required validation shows a visible inline error and does
|
|
11
|
+
* NOT submit; server errors render verbatim in a role=alert region;
|
|
12
|
+
* - idempotency: one key per user INTENT (minted at mount and after each
|
|
13
|
+
* success), constant across retries of a failed submit — the host dedupes;
|
|
14
|
+
* - visible states throughout: pending disables the button, success is a
|
|
15
|
+
* role=status message, failure a role=alert — never a silent outcome.
|
|
16
|
+
*/
|
|
17
|
+
import { type TemplateResult } from "lit";
|
|
18
|
+
import { type BlockManifest, type BlockPackage, type Locale } from "@batterai/contract";
|
|
19
|
+
import { BatteryBlockElement } from "../base-element";
|
|
20
|
+
export declare function createFormManifest(locale?: Locale | string): BlockManifest;
|
|
21
|
+
export declare const formManifest: BlockManifest;
|
|
22
|
+
export interface FormFieldSpec {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly label: string;
|
|
25
|
+
readonly required?: boolean;
|
|
26
|
+
readonly multiline?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** Parse the literal fieldsJson prop; malformed JSON is a VISIBLE config
|
|
29
|
+
* error, not a silently empty form. */
|
|
30
|
+
export declare function parseFormFields(raw: unknown, locale?: Locale | string): FormFieldSpec[] | Error;
|
|
31
|
+
type Status = {
|
|
32
|
+
kind: "idle";
|
|
33
|
+
} | {
|
|
34
|
+
kind: "pending";
|
|
35
|
+
} | {
|
|
36
|
+
kind: "success";
|
|
37
|
+
message: string;
|
|
38
|
+
} | {
|
|
39
|
+
kind: "error";
|
|
40
|
+
message: string;
|
|
41
|
+
};
|
|
42
|
+
export declare class BatteryForm extends BatteryBlockElement {
|
|
43
|
+
static styles: import("lit").CSSResult;
|
|
44
|
+
static properties: {
|
|
45
|
+
status: {
|
|
46
|
+
state: boolean;
|
|
47
|
+
};
|
|
48
|
+
fieldErrors: {
|
|
49
|
+
state: boolean;
|
|
50
|
+
};
|
|
51
|
+
config: {
|
|
52
|
+
attribute: string;
|
|
53
|
+
type: ObjectConstructor;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
status: Status;
|
|
57
|
+
fieldErrors: Record<string, string>;
|
|
58
|
+
/** One key per user INTENT: constant across retries, rotated on success. */
|
|
59
|
+
private idempotencyKey;
|
|
60
|
+
constructor();
|
|
61
|
+
private submit;
|
|
62
|
+
render(): TemplateResult;
|
|
63
|
+
}
|
|
64
|
+
export declare function createFormPackage(locale?: Locale | string): BlockPackage;
|
|
65
|
+
export declare const formPackage: BlockPackage;
|
|
66
|
+
export {};
|
|
67
|
+
//# sourceMappingURL=form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/blocks/form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAC9D,OAAO,EAAa,KAAK,aAAa,EAAE,KAAK,YAAY,EAAc,KAAK,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC/G,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAsBtD,wBAAgB,kBAAkB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,CAoB1E;AAED,eAAO,MAAM,YAAY,EAAE,aAAwC,CAAC;AAEpE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;uCACuC;AACvC,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,EAAE,GAAG,KAAK,CAc/F;AAED,KAAK,MAAM,GACP;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,qBAAa,WAAY,SAAQ,mBAAmB;IAClD,MAAM,CAAC,MAAM,0BASX;IAEF,MAAM,CAAC,UAAU;;;;;;;;;;;MAIf;IAEM,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,4EAA4E;IAC5E,OAAO,CAAC,cAAc,CAAuB;;YAQ/B,MAAM;IAgDX,MAAM,IAAI,cAAc;CAoClC;AAED,wBAAgB,iBAAiB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,YAAY,CAOxE;AAED,eAAO,MAAM,WAAW,EAAE,YAAsC,CAAC"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-party `battery.form` (Tier 3, hydration "partial") — the v1 form
|
|
3
|
+
* block posts to a host action endpoint, which routes through HostApi.mutate:
|
|
4
|
+
* - fields render from a literal `fieldsJson` spec (name/label/required/
|
|
5
|
+
* multiline) — SSR'd, so the form is usable at first paint;
|
|
6
|
+
* - the write TARGET is never wire-authored: the block posts only
|
|
7
|
+
* { formId, instanceId, idempotencyKey, values } to the host `action`
|
|
8
|
+
* endpoint; the host maps formId -> registered target server-side and
|
|
9
|
+
* runs checkWriteAuthz (tenant-scoped, invariant D order);
|
|
10
|
+
* - client-side required validation shows a visible inline error and does
|
|
11
|
+
* NOT submit; server errors render verbatim in a role=alert region;
|
|
12
|
+
* - idempotency: one key per user INTENT (minted at mount and after each
|
|
13
|
+
* success), constant across retries of a failed submit — the host dedupes;
|
|
14
|
+
* - visible states throughout: pending disables the button, success is a
|
|
15
|
+
* role=status message, failure a role=alert — never a silent outcome.
|
|
16
|
+
*/
|
|
17
|
+
import { css, html, nothing } from "lit";
|
|
18
|
+
import { fieldName } from "@batterai/contract";
|
|
19
|
+
import { BatteryBlockElement } from "../base-element.js";
|
|
20
|
+
import { defineBlock } from "../define.js";
|
|
21
|
+
import { blockSdkMessage } from "../i18n.js";
|
|
22
|
+
function fields(locale) {
|
|
23
|
+
return [
|
|
24
|
+
{ name: fieldName("title"), label: blockSdkMessage(locale, "form.field.title.label"), valueType: "string", cardinality: "one", bindable: true },
|
|
25
|
+
{
|
|
26
|
+
name: fieldName("fieldsJson"),
|
|
27
|
+
label: blockSdkMessage(locale, "form.field.fieldsJson.label"),
|
|
28
|
+
help: blockSdkMessage(locale, "form.field.fieldsJson.help"),
|
|
29
|
+
valueType: "json",
|
|
30
|
+
cardinality: "one",
|
|
31
|
+
bindable: false,
|
|
32
|
+
},
|
|
33
|
+
{ name: fieldName("submitLabel"), label: blockSdkMessage(locale, "form.field.submitLabel.label"), valueType: "string", cardinality: "one", bindable: false },
|
|
34
|
+
{ name: fieldName("successMessage"), label: blockSdkMessage(locale, "form.field.successMessage.label"), valueType: "string", cardinality: "one", bindable: false },
|
|
35
|
+
{ name: fieldName("action"), label: blockSdkMessage(locale, "form.field.action.label"), valueType: "string", cardinality: "one", bindable: false },
|
|
36
|
+
{ name: fieldName("formId"), label: blockSdkMessage(locale, "form.field.formId.label"), valueType: "string", cardinality: "one", bindable: false },
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
export function createFormManifest(locale) {
|
|
40
|
+
return {
|
|
41
|
+
type: "battery.form",
|
|
42
|
+
version: "1.0.0",
|
|
43
|
+
source: "battery",
|
|
44
|
+
label: blockSdkMessage(locale, "form.manifest.label"),
|
|
45
|
+
summary: blockSdkMessage(locale, "form.manifest.summary"),
|
|
46
|
+
category: "input",
|
|
47
|
+
icon: "form",
|
|
48
|
+
fields: fields(locale),
|
|
49
|
+
defaultProps: {
|
|
50
|
+
title: "",
|
|
51
|
+
fieldsJson: "[]",
|
|
52
|
+
submitLabel: blockSdkMessage(locale, "form.default.submitLabel"),
|
|
53
|
+
successMessage: blockSdkMessage(locale, "form.default.successMessage"),
|
|
54
|
+
action: "/__form",
|
|
55
|
+
formId: "",
|
|
56
|
+
},
|
|
57
|
+
capabilities: { ssr: true, hydration: "partial" },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export const formManifest = createFormManifest("en");
|
|
61
|
+
/** Parse the literal fieldsJson prop; malformed JSON is a VISIBLE config
|
|
62
|
+
* error, not a silently empty form. */
|
|
63
|
+
export function parseFormFields(raw, locale) {
|
|
64
|
+
if (typeof raw !== "string")
|
|
65
|
+
return new Error(blockSdkMessage(locale, "form.parse.missing"));
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(raw);
|
|
68
|
+
if (!Array.isArray(parsed))
|
|
69
|
+
return new Error(blockSdkMessage(locale, "form.parse.list"));
|
|
70
|
+
for (const f of parsed) {
|
|
71
|
+
if (typeof f?.name !== "string" || !f.name || typeof f?.label !== "string") {
|
|
72
|
+
return new Error(blockSdkMessage(locale, "form.parse.field"));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return parsed;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return new Error(blockSdkMessage(locale, "form.parse.invalidJson"));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export class BatteryForm extends BatteryBlockElement {
|
|
82
|
+
static styles = css `
|
|
83
|
+
form { font-family: system-ui, sans-serif; display: grid; gap: 8px; max-width: 480px; }
|
|
84
|
+
label { display: grid; gap: 2px; font-size: 14px; }
|
|
85
|
+
input, textarea { font: inherit; padding: 6px 8px; border: 1px solid #bbb; border-radius: 4px; }
|
|
86
|
+
button { justify-self: start; font: inherit; padding: 6px 16px; border-radius: 4px; border: 1px solid #666; cursor: pointer; }
|
|
87
|
+
button[disabled] { opacity: 0.6; cursor: wait; }
|
|
88
|
+
.be-form-error { background: #f7c1c1; padding: 6px 10px; border-radius: 4px; font-size: 14px; }
|
|
89
|
+
.be-form-success { background: #c9ecc9; padding: 6px 10px; border-radius: 4px; font-size: 14px; }
|
|
90
|
+
.be-field-error { color: #a4262c; font-size: 12px; }
|
|
91
|
+
`;
|
|
92
|
+
static properties = {
|
|
93
|
+
...BatteryBlockElement.properties,
|
|
94
|
+
status: { state: true },
|
|
95
|
+
fieldErrors: { state: true },
|
|
96
|
+
};
|
|
97
|
+
/** One key per user INTENT: constant across retries, rotated on success. */
|
|
98
|
+
idempotencyKey = crypto.randomUUID();
|
|
99
|
+
constructor() {
|
|
100
|
+
super();
|
|
101
|
+
this.status = { kind: "idle" };
|
|
102
|
+
this.fieldErrors = {};
|
|
103
|
+
}
|
|
104
|
+
async submit(e) {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
const c = this.config;
|
|
107
|
+
if (!c || this.status.kind === "pending")
|
|
108
|
+
return;
|
|
109
|
+
const specs = parseFormFields(c.props.fieldsJson, c.context.locale);
|
|
110
|
+
if (specs instanceof Error)
|
|
111
|
+
return;
|
|
112
|
+
const form = e.target;
|
|
113
|
+
const values = {};
|
|
114
|
+
const errors = {};
|
|
115
|
+
for (const spec of specs) {
|
|
116
|
+
const el = form.elements.namedItem(spec.name);
|
|
117
|
+
const value = el?.value?.trim() ?? "";
|
|
118
|
+
if (spec.required && !value) {
|
|
119
|
+
errors[spec.name] = blockSdkMessage(c.context.locale, "form.validation.required", { label: spec.label });
|
|
120
|
+
}
|
|
121
|
+
values[spec.name] = value;
|
|
122
|
+
}
|
|
123
|
+
this.fieldErrors = errors;
|
|
124
|
+
if (Object.keys(errors).length > 0)
|
|
125
|
+
return; // visible inline, no request
|
|
126
|
+
this.status = { kind: "pending" };
|
|
127
|
+
try {
|
|
128
|
+
const res = await fetch(String(c.props.action ?? "/__form"), {
|
|
129
|
+
method: "POST",
|
|
130
|
+
headers: { "Content-Type": "application/json" },
|
|
131
|
+
body: JSON.stringify({
|
|
132
|
+
formId: String(c.props.formId ?? ""),
|
|
133
|
+
instanceId: c.data.instanceId,
|
|
134
|
+
idempotencyKey: this.idempotencyKey,
|
|
135
|
+
values,
|
|
136
|
+
}),
|
|
137
|
+
});
|
|
138
|
+
if (res.ok) {
|
|
139
|
+
this.idempotencyKey = crypto.randomUUID(); // next intent = next key
|
|
140
|
+
this.status = {
|
|
141
|
+
kind: "success",
|
|
142
|
+
message: String(c.props.successMessage ?? blockSdkMessage(c.context.locale, "form.status.processed")),
|
|
143
|
+
};
|
|
144
|
+
form.reset();
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
this.status = { kind: "error", message: await res.text() };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
this.status = { kind: "error", message: blockSdkMessage(c.context.locale, "form.status.connectionFailed") };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
render() {
|
|
155
|
+
const c = this.config;
|
|
156
|
+
if (!c)
|
|
157
|
+
return html `<p class="be-unconfigured">${blockSdkMessage(undefined, "block.unconfigured")}</p>`;
|
|
158
|
+
const title = typeof c.props.title === "string" ? c.props.title : "";
|
|
159
|
+
const specs = parseFormFields(c.props.fieldsJson, c.context.locale);
|
|
160
|
+
if (specs instanceof Error) {
|
|
161
|
+
return html `<p class="be-form-error" role="alert">
|
|
162
|
+
${blockSdkMessage(c.context.locale, "form.config.invalid", { message: specs.message })}
|
|
163
|
+
</p>`;
|
|
164
|
+
}
|
|
165
|
+
return html `<form @submit=${(e) => void this.submit(e)} novalidate>
|
|
166
|
+
${title ? html `<h3>${title}</h3>` : nothing}
|
|
167
|
+
${specs.map((spec) => html `<label>
|
|
168
|
+
${spec.label}${spec.required ? " *" : ""}
|
|
169
|
+
${spec.multiline
|
|
170
|
+
? html `<textarea name=${spec.name} rows="3"></textarea>`
|
|
171
|
+
: html `<input name=${spec.name} type="text" />`}
|
|
172
|
+
${this.fieldErrors[spec.name]
|
|
173
|
+
? html `<span class="be-field-error" role="alert">${this.fieldErrors[spec.name]}</span>`
|
|
174
|
+
: nothing}
|
|
175
|
+
</label>`)}
|
|
176
|
+
<button type="submit" ?disabled=${this.status.kind === "pending"}>
|
|
177
|
+
${this.status.kind === "pending"
|
|
178
|
+
? blockSdkMessage(c.context.locale, "form.status.pending")
|
|
179
|
+
: String(c.props.submitLabel ?? blockSdkMessage(c.context.locale, "form.default.submitLabel"))}
|
|
180
|
+
</button>
|
|
181
|
+
${this.status.kind === "success"
|
|
182
|
+
? html `<p class="be-form-success" role="status">${this.status.message}</p>`
|
|
183
|
+
: nothing}
|
|
184
|
+
${this.status.kind === "error"
|
|
185
|
+
? html `<p class="be-form-error" role="alert">${this.status.message}</p>`
|
|
186
|
+
: nothing}
|
|
187
|
+
</form>`;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
export function createFormPackage(locale) {
|
|
191
|
+
const manifest = createFormManifest(locale);
|
|
192
|
+
return {
|
|
193
|
+
manifest,
|
|
194
|
+
stylesheets: [new URL("./form.css", import.meta.url).href],
|
|
195
|
+
defineElement: defineBlock(manifest, BatteryForm),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
export const formPackage = createFormPackage("en");
|
|
199
|
+
//# sourceMappingURL=form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form.js","sourceRoot":"","sources":["../../src/blocks/form.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
be-battery-image-v1 { display: block; }
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* First-party `battery.image` (Tier 1, hydration "none" — 0 kb client JS):
|
|
3
|
+
* a bound image through @batterai/media (plan §8 Phase 7). The block is a pure
|
|
4
|
+
* function of RESOLVED props + host-supplied media data:
|
|
5
|
+
* - `assetId`/`alt`/`caption` props are bindable like any other prop;
|
|
6
|
+
* - the HOST resolves assetId -> src/srcset/dimensions via its MediaStore and
|
|
7
|
+
* injects the result as `config.data.media` (blocks never do I/O — the
|
|
8
|
+
* media lookup is host render machinery, shared by page + fragment +
|
|
9
|
+
* canvas so all three agree, invariant A);
|
|
10
|
+
* - an assetId without media data renders a VISIBLE diagnostic (`be-media-
|
|
11
|
+
* missing`, role=alert) — never a broken <img>, never a silent gap;
|
|
12
|
+
* - alt precedence: bound/authored `alt` prop, else the asset's own alt (the
|
|
13
|
+
* store validates alt at upload, so the floor is never empty).
|
|
14
|
+
*/
|
|
15
|
+
import { type TemplateResult } from "lit";
|
|
16
|
+
import { type BlockManifest, type BlockPackage, type Locale } from "@batterai/contract";
|
|
17
|
+
import { BatteryBlockElement } from "../base-element";
|
|
18
|
+
export declare function createImageManifest(locale?: Locale | string): BlockManifest;
|
|
19
|
+
export declare const imageManifest: BlockManifest;
|
|
20
|
+
export type ImageView = "single" | "carousel" | "grid";
|
|
21
|
+
/** The host-injected media payload (`config.data.media` or `.images`). */
|
|
22
|
+
export interface ImageMediaData {
|
|
23
|
+
readonly src: string;
|
|
24
|
+
readonly srcset?: string;
|
|
25
|
+
readonly width: number;
|
|
26
|
+
readonly height: number;
|
|
27
|
+
readonly alt: string;
|
|
28
|
+
readonly title?: string;
|
|
29
|
+
readonly caption?: string;
|
|
30
|
+
readonly href?: string;
|
|
31
|
+
readonly focal?: {
|
|
32
|
+
readonly x: number;
|
|
33
|
+
readonly y: number;
|
|
34
|
+
};
|
|
35
|
+
readonly aspectRatio?: string;
|
|
36
|
+
readonly sizing?: {
|
|
37
|
+
readonly width?: string;
|
|
38
|
+
readonly height?: string;
|
|
39
|
+
readonly objectFit?: "cover" | "contain" | "fill";
|
|
40
|
+
};
|
|
41
|
+
readonly opacity?: number;
|
|
42
|
+
}
|
|
43
|
+
export declare class BatteryImage extends BatteryBlockElement {
|
|
44
|
+
static styles: import("lit").CSSResult;
|
|
45
|
+
render(): TemplateResult;
|
|
46
|
+
}
|
|
47
|
+
export declare function createImagePackage(locale?: Locale | string): BlockPackage;
|
|
48
|
+
export declare const imagePackage: BlockPackage;
|
|
49
|
+
//# sourceMappingURL=image.d.ts.map
|