@saacms/blocks-essentials 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.
Files changed (44) hide show
  1. package/README.md +59 -0
  2. package/dist/code-block/CodeBlock.block.d.ts +25 -0
  3. package/dist/code-block/CodeBlock.block.d.ts.map +1 -0
  4. package/dist/code-block/CodeBlock.d.ts +34 -0
  5. package/dist/code-block/CodeBlock.d.ts.map +1 -0
  6. package/dist/hero/Hero.block.d.ts +28 -0
  7. package/dist/hero/Hero.block.d.ts.map +1 -0
  8. package/dist/hero/Hero.block.js +25 -0
  9. package/dist/hero/Hero.d.ts +23 -0
  10. package/dist/hero/Hero.d.ts.map +1 -0
  11. package/dist/hero/Hero.js +82 -0
  12. package/dist/image/Image.block.d.ts +28 -0
  13. package/dist/image/Image.block.d.ts.map +1 -0
  14. package/dist/image/Image.d.ts +28 -0
  15. package/dist/image/Image.d.ts.map +1 -0
  16. package/dist/index.d.ts +23 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +778 -0
  19. package/dist/internal/html-element-base.d.ts +17 -0
  20. package/dist/internal/html-element-base.d.ts.map +1 -0
  21. package/dist/internal/safe-href.d.ts +17 -0
  22. package/dist/internal/safe-href.d.ts.map +1 -0
  23. package/dist/internal/sanitize-html.d.ts +25 -0
  24. package/dist/internal/sanitize-html.d.ts.map +1 -0
  25. package/dist/pricing-table/PricingTable.block.d.ts +36 -0
  26. package/dist/pricing-table/PricingTable.block.d.ts.map +1 -0
  27. package/dist/pricing-table/PricingTable.d.ts +37 -0
  28. package/dist/pricing-table/PricingTable.d.ts.map +1 -0
  29. package/dist/raw-html/RawHtml.block.d.ts +31 -0
  30. package/dist/raw-html/RawHtml.block.d.ts.map +1 -0
  31. package/dist/raw-html/RawHtml.d.ts +31 -0
  32. package/dist/raw-html/RawHtml.d.ts.map +1 -0
  33. package/dist/register.d.ts +19 -0
  34. package/dist/register.d.ts.map +1 -0
  35. package/dist/register.js +18 -0
  36. package/dist/rich-text/RichText.block.d.ts +22 -0
  37. package/dist/rich-text/RichText.block.d.ts.map +1 -0
  38. package/dist/rich-text/RichText.d.ts +25 -0
  39. package/dist/rich-text/RichText.d.ts.map +1 -0
  40. package/dist/video/Video.block.d.ts +31 -0
  41. package/dist/video/Video.block.d.ts.map +1 -0
  42. package/dist/video/Video.d.ts +54 -0
  43. package/dist/video/Video.d.ts.map +1 -0
  44. package/package.json +36 -0
@@ -0,0 +1,17 @@
1
+ /**
2
+ * SSR-safe `HTMLElement` base class.
3
+ *
4
+ * Built-in blocks are authored as Web Components (ADR 0004 Mode 1), but their
5
+ * modules are also imported in a DOM-less context — the saacms compile step (Node)
6
+ * imports the `*.block.ts` defs, and the test suite imports the element modules to
7
+ * unit-test the pure security helpers (sanitizer / escaper).
8
+ *
9
+ * `class X extends HTMLElement` is evaluated at module load. In Node/Bun there is
10
+ * no global `HTMLElement`, so `extends HTMLElement` would throw at import time and
11
+ * make the whole package un-importable outside a browser. Extending this base
12
+ * instead resolves to the real `HTMLElement` in the browser and an inert stub
13
+ * elsewhere, so the modules import cleanly everywhere. The stub is never
14
+ * instantiated as a custom element (no host calls `customElements.define` in Node).
15
+ */
16
+ export declare const HTMLElementBase: typeof HTMLElement;
17
+ //# sourceMappingURL=html-element-base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-element-base.d.ts","sourceRoot":"","sources":["../../src/internal/html-element-base.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,eAAO,MAAM,eAAe,EAAE,OAAO,WAGY,CAAA"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `safeHref` — URL-scheme allowlist for user-supplied link/media targets.
3
+ *
4
+ * Shared by the PricingTable CTA `<a href>` and the Video `<video src/poster>`.
5
+ * Mirrors the sanitizer's discipline (conservative allowlist, not a denylist):
6
+ * only `http:` / `https:` absolute URLs and scheme-less relative URLs survive.
7
+ * Everything else — `javascript:`, `data:`, `vbscript:`, `file:`, `mailto:`,
8
+ * etc. — is NEUTRALIZED by returning the empty string `""`.
9
+ *
10
+ * Returning `""` (not `"#"`) is the documented contract: callers MUST treat an
11
+ * empty result as "no usable target" and render accordingly (omit the `href`,
12
+ * skip the `src`) rather than emitting a broken/exploitable attribute.
13
+ *
14
+ * Dependency-free and DOM-less so it is unit-testable without a browser.
15
+ */
16
+ export declare function safeHref(value: string): string;
17
+ //# sourceMappingURL=safe-href.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-href.d.ts","sourceRoot":"","sources":["../../src/internal/safe-href.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAa9C"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Shared allowlist HTML sanitizer — the SINGLE source of truth.
3
+ *
4
+ * Originally authored inside `rich-text/RichText.ts`; extracted here so the
5
+ * RichText block AND the RawHtml escape-hatch block sanitize identically. A
6
+ * forked second copy is the classic XSS-regression trap, so this lives in
7
+ * `internal/` and is imported by both. `RichText.ts` re-exports it to keep its
8
+ * existing public surface + unit tests working unchanged.
9
+ *
10
+ * Dependency-free and DOM-less so it runs in Node (compile step / tests) and the
11
+ * browser identically. The element is an exported pure function so the security
12
+ * boundary is unit-testable without a DOM.
13
+ */
14
+ /**
15
+ * Allowlist sanitizer for untrusted HTML. Dependency-free and DOM-less
16
+ * so it runs in Node (compile step / tests) and the browser identically.
17
+ *
18
+ * - `<script>` / `<style>` elements are removed wholesale (tag + contents).
19
+ * - HTML comments are removed.
20
+ * - Tags not in {@link ALLOWED_TAGS} have their markup stripped (text is kept).
21
+ * - Allowed tags keep only allowlisted attributes; `on*` handlers are always
22
+ * dropped and `javascript:` / `vbscript:` / `data:` / `file:` URLs are neutralized.
23
+ */
24
+ export declare function sanitizeRichTextHtml(input: string): string;
25
+ //# sourceMappingURL=sanitize-html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize-html.d.ts","sourceRoot":"","sources":["../../src/internal/sanitize-html.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAoDH;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAqB1D"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * PricingTable block definition (saacms Block).
3
+ *
4
+ * Authoring mode: web-component (ADR 0004 Mode 1). The canonical `component`
5
+ * field is the custom-element TAG NAME (`saacms-pricing-table`), so this def
6
+ * imports cleanly in the DOM-less compile step.
7
+ *
8
+ * `price` is a PRE-FORMATTED display string (e.g. "$9/mo"). v1 deliberately does
9
+ * no currency math / locale formatting — the operator types exactly what renders.
10
+ * Tier `name` / `features` / CTA text are untrusted and are emitted via
11
+ * `textContent`; the CTA `href` is run through `safeHref` (see PricingTable.ts).
12
+ */
13
+ import { Schema } from "effect";
14
+ declare const PricingTableSchema: Schema.Struct<{
15
+ tiers: Schema.Array$<Schema.Struct<{
16
+ name: typeof Schema.String;
17
+ price: typeof Schema.String;
18
+ features: Schema.Array$<typeof Schema.String>;
19
+ highlighted: Schema.optional<typeof Schema.Boolean>;
20
+ ctaLabel: Schema.optional<typeof Schema.String>;
21
+ ctaHref: Schema.optional<typeof Schema.String>;
22
+ }>>;
23
+ }>;
24
+ export type PricingTableProps = Schema.Schema.Type<typeof PricingTableSchema>;
25
+ export declare const PricingTableBlock: import("@saacms/core").BlockDef<Schema.Struct<{
26
+ tiers: Schema.Array$<Schema.Struct<{
27
+ name: typeof Schema.String;
28
+ price: typeof Schema.String;
29
+ features: Schema.Array$<typeof Schema.String>;
30
+ highlighted: Schema.optional<typeof Schema.Boolean>;
31
+ ctaLabel: Schema.optional<typeof Schema.String>;
32
+ ctaHref: Schema.optional<typeof Schema.String>;
33
+ }>>;
34
+ }>>;
35
+ export {};
36
+ //# sourceMappingURL=PricingTable.block.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PricingTable.block.d.ts","sourceRoot":"","sources":["../../src/pricing-table/PricingTable.block.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAG/B,QAAA,MAAM,kBAAkB;;;;;;;;;EAWtB,CAAA;AAEF,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAE7E,eAAO,MAAM,iBAAiB;;;;;;;;;GAK5B,CAAA"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * <saacms-pricing-table> — built-in PricingTable block (ADR 0004 Mode 1).
3
+ *
4
+ * The `tiers` prop is a structured array, not a scalar attribute. It is accepted
5
+ * two ways (ADR 0004 Mode 1 attribute+property contract):
6
+ * - JS property `tiers` (the Puck canvas sets this directly), and
7
+ * - a `tiers` attribute carrying the same value as a JSON string.
8
+ *
9
+ * Every tier field is UNTRUSTED. The whole subtree is built with
10
+ * `createElement` + `textContent` (never `innerHTML` of user data), so the
11
+ * browser escapes names / prices / features for us. The CTA `href` is the one
12
+ * URL-bearing value and is passed through the shared `safeHref` allowlist; an
13
+ * empty result means "no usable target" so the CTA renders as plain text.
14
+ *
15
+ * `safeHref` is re-exported for hosts that render this block to a static string.
16
+ */
17
+ import { HTMLElementBase } from "../internal/html-element-base.ts";
18
+ export { safeHref } from "../internal/safe-href.ts";
19
+ interface Tier {
20
+ readonly name: string;
21
+ readonly price: string;
22
+ readonly features: readonly string[];
23
+ readonly highlighted?: boolean;
24
+ readonly ctaLabel?: string;
25
+ readonly ctaHref?: string;
26
+ }
27
+ export declare class PricingTable extends HTMLElementBase {
28
+ static get observedAttributes(): readonly string[];
29
+ private propTiers;
30
+ get tiers(): Tier[];
31
+ set tiers(value: readonly Tier[] | null);
32
+ private parseAttr;
33
+ connectedCallback(): void;
34
+ attributeChangedCallback(name: string, _oldValue: string | null, _newValue: string | null): void;
35
+ private render;
36
+ }
37
+ //# sourceMappingURL=PricingTable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PricingTable.d.ts","sourceRoot":"","sources":["../../src/pricing-table/PricingTable.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAA;AAGlE,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAKnD,UAAU,IAAI;IACZ,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAC1B;AAyBD,qBAAa,YAAa,SAAQ,eAAe;IAC/C,MAAM,KAAK,kBAAkB,IAAI,SAAS,MAAM,EAAE,CAEjD;IAED,OAAO,CAAC,SAAS,CAAsB;IAGvC,IAAI,KAAK,IAAI,IAAI,EAAE,CAGlB;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,IAAI,EAGtC;IAED,OAAO,CAAC,SAAS;IAUjB,iBAAiB,IAAI,IAAI;IAIzB,wBAAwB,CACtB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,IAAI;IAOP,OAAO,CAAC,MAAM;CAmDf"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * RawHtml block definition (saacms Block) — ⚠️ DELIBERATE ESCAPE HATCH ⚠️.
3
+ *
4
+ * Authoring mode: web-component (ADR 0004 Mode 1). The canonical `component`
5
+ * field is the custom-element TAG NAME (`saacms-raw-html`).
6
+ *
7
+ * FOOTGUN WARNING: with `trusted: true` this block injects its `html` prop
8
+ * VERBATIM via `innerHTML` with NO sanitization. That is stored XSS by design
9
+ * and must only ever hold FIRST-PARTY content authored by a trusted operator.
10
+ * The default (`trusted` absent / not `true`) routes `html` through the SAME
11
+ * allowlist sanitizer RichText uses (single source of truth in
12
+ * `internal/sanitize-html.ts`) — there is no second, forkable sanitizer.
13
+ *
14
+ * When `trusted: true` the element additionally sets `data-saacms-trusted="true"`
15
+ * on itself and `console.warn`s once per instance. The admin UI is expected to
16
+ * surface an explicit confirmation before allowing `trusted: true` (out of scope
17
+ * here — this def only declares the schema; the warn + data-attr live in the
18
+ * element).
19
+ */
20
+ import { Schema } from "effect";
21
+ declare const RawHtmlSchema: Schema.Struct<{
22
+ html: typeof Schema.String;
23
+ trusted: Schema.optional<typeof Schema.Boolean>;
24
+ }>;
25
+ export type RawHtmlProps = Schema.Schema.Type<typeof RawHtmlSchema>;
26
+ export declare const RawHtmlBlock: import("@saacms/core").BlockDef<Schema.Struct<{
27
+ html: typeof Schema.String;
28
+ trusted: Schema.optional<typeof Schema.Boolean>;
29
+ }>>;
30
+ export {};
31
+ //# sourceMappingURL=RawHtml.block.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RawHtml.block.d.ts","sourceRoot":"","sources":["../../src/raw-html/RawHtml.block.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAG/B,QAAA,MAAM,aAAa;;;EAGjB,CAAA;AAEF,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,aAAa,CAAC,CAAA;AAEnE,eAAO,MAAM,YAAY;;;GAKvB,CAAA"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * <saacms-raw-html> — built-in RawHtml block (ADR 0004 Mode 1).
3
+ *
4
+ * The deliberate escape hatch. Two modes:
5
+ *
6
+ * - DEFAULT (`trusted` !== true): the `html` prop is UNTRUSTED and is run
7
+ * through the SAME allowlist sanitizer RichText uses, imported from
8
+ * `../internal/sanitize-html.ts`. There is exactly one sanitizer in this
9
+ * package — RawHtml does NOT fork its own copy (a divergent second
10
+ * sanitizer is the classic XSS-regression trap).
11
+ *
12
+ * - `trusted === true`: ⚠️ the `html` is injected VERBATIM via `innerHTML`
13
+ * with NO sanitization. This is stored XSS by design and is only safe for
14
+ * FIRST-PARTY content. The element flags itself with
15
+ * `data-saacms-trusted="true"` and `console.warn`s once per instance so the
16
+ * bypass is observable in logs / DOM audits. The admin UI gates this with a
17
+ * confirmation (out of scope here).
18
+ */
19
+ import { HTMLElementBase } from "../internal/html-element-base.ts";
20
+ export declare class RawHtml extends HTMLElementBase {
21
+ static get observedAttributes(): readonly string[];
22
+ private warnedTrusted;
23
+ get html(): string;
24
+ set html(value: string);
25
+ get trusted(): boolean;
26
+ set trusted(value: boolean);
27
+ connectedCallback(): void;
28
+ attributeChangedCallback(name: string, _oldValue: string | null, _newValue: string | null): void;
29
+ private render;
30
+ }
31
+ //# sourceMappingURL=RawHtml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RawHtml.d.ts","sourceRoot":"","sources":["../../src/raw-html/RawHtml.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAA;AASlE,qBAAa,OAAQ,SAAQ,eAAe;IAC1C,MAAM,KAAK,kBAAkB,IAAI,SAAS,MAAM,EAAE,CAEjD;IAGD,OAAO,CAAC,aAAa,CAAQ;IAG7B,IAAI,IAAI,IAAI,MAAM,CAEjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED,IAAI,OAAO,IAAI,OAAO,CAErB;IACD,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,iBAAiB,IAAI,IAAI;IAIzB,wBAAwB,CACtB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,IAAI;IAKP,OAAO,CAAC,MAAM;CAkBf"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Runtime registration of every built-in custom element shipped by this package.
3
+ *
4
+ * Idempotent: re-registration is a no-op (custom elements throw if defined twice,
5
+ * so we guard with `customElements.get(...)`). Safe to call from a host's app boot
6
+ * regardless of how many times other code may have already invoked it.
7
+ *
8
+ * The {@link REGISTRATIONS} table is exported so the tag↔ctor mapping can be
9
+ * introspected (tests, tooling) without invoking `customElements.define` — which
10
+ * is unavailable outside a browser. `register()` itself additionally no-ops in a
11
+ * DOM-less context so importing and probing this module never throws.
12
+ */
13
+ export interface ElementRegistration {
14
+ readonly tag: string;
15
+ readonly ctor: CustomElementConstructor;
16
+ }
17
+ export declare const REGISTRATIONS: readonly ElementRegistration[];
18
+ export declare function register(): void;
19
+ //# sourceMappingURL=register.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAA;CACxC;AAED,eAAO,MAAM,aAAa,EAAE,SAAS,mBAAmB,EAQvD,CAAA;AAED,wBAAgB,QAAQ,IAAI,IAAI,CAO/B"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Runtime registration of every built-in custom element shipped by this package.
3
+ *
4
+ * Idempotent: re-registration is a no-op (custom elements throw if defined twice,
5
+ * so we guard with `customElements.get(...)`). Safe to call from a host's app boot
6
+ * regardless of how many times other code may have already invoked it.
7
+ */
8
+ import { Hero } from "./hero/Hero.ts";
9
+ const REGISTRATIONS = [
10
+ { tag: "saacms-hero", ctor: Hero },
11
+ ];
12
+ export function register() {
13
+ for (const { tag, ctor } of REGISTRATIONS) {
14
+ if (customElements.get(tag) === undefined) {
15
+ customElements.define(tag, ctor);
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * RichText block definition (saacms Block).
3
+ *
4
+ * Authoring mode: web-component (ADR 0004 Mode 1). The canonical `component`
5
+ * field is the custom-element TAG NAME (`saacms-rich-text`), so this def stays
6
+ * importable in the DOM-less compile step (it never imports the element class).
7
+ *
8
+ * The `html` prop is operator-supplied stored HTML and is therefore untrusted: the
9
+ * element module sanitizes it through an allowlist before it ever reaches the DOM.
10
+ */
11
+ import { Schema } from "effect";
12
+ declare const RichTextSchema: Schema.Struct<{
13
+ html: typeof Schema.String;
14
+ align: Schema.optional<Schema.Literal<["left", "center", "right"]>>;
15
+ }>;
16
+ export type RichTextProps = Schema.Schema.Type<typeof RichTextSchema>;
17
+ export declare const RichTextBlock: import("@saacms/core").BlockDef<Schema.Struct<{
18
+ html: typeof Schema.String;
19
+ align: Schema.optional<Schema.Literal<["left", "center", "right"]>>;
20
+ }>>;
21
+ export {};
22
+ //# sourceMappingURL=RichText.block.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RichText.block.d.ts","sourceRoot":"","sources":["../../src/rich-text/RichText.block.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAG/B,QAAA,MAAM,cAAc;;;EAGlB,CAAA;AAEF,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,cAAc,CAAC,CAAA;AAErE,eAAO,MAAM,aAAa;;;GAKxB,CAAA"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * <saacms-rich-text> — built-in RichText block (ADR 0004 Mode 1).
3
+ *
4
+ * Renders operator-supplied HTML into the light DOM. That HTML is UNTRUSTED
5
+ * (stored XSS is the exact risk), so it is run through `sanitizeRichTextHtml`
6
+ * before it touches `innerHTML`.
7
+ *
8
+ * The sanitizer itself now lives in `../internal/sanitize-html.ts` as the SINGLE
9
+ * source of truth (shared with the RawHtml escape-hatch block — a forked second
10
+ * copy is the classic XSS-regression trap). It is re-exported here unchanged so
11
+ * RichText's existing public surface (`index.ts`) and unit tests keep working.
12
+ */
13
+ import { HTMLElementBase } from "../internal/html-element-base.ts";
14
+ export { sanitizeRichTextHtml } from "../internal/sanitize-html.ts";
15
+ export declare class RichText extends HTMLElementBase {
16
+ static get observedAttributes(): readonly string[];
17
+ get html(): string;
18
+ set html(value: string);
19
+ get align(): "left" | "center" | "right";
20
+ set align(value: "left" | "center" | "right" | null);
21
+ connectedCallback(): void;
22
+ attributeChangedCallback(name: string, _oldValue: string | null, _newValue: string | null): void;
23
+ private render;
24
+ }
25
+ //# sourceMappingURL=RichText.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RichText.d.ts","sourceRoot":"","sources":["../../src/rich-text/RichText.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAA;AAGlE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAA;AASnE,qBAAa,QAAS,SAAQ,eAAe;IAC3C,MAAM,KAAK,kBAAkB,IAAI,SAAS,MAAM,EAAE,CAEjD;IAID,IAAI,IAAI,IAAI,MAAM,CAEjB;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,MAAM,EAErB;IAED,IAAI,KAAK,IAAI,MAAM,GAAG,QAAQ,GAAG,OAAO,CAEvC;IACD,IAAI,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,IAAI,EAMlD;IAED,iBAAiB,IAAI,IAAI;IAIzB,wBAAwB,CACtB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,IAAI;IAOP,OAAO,CAAC,MAAM;CAMf"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Video block definition (saacms Block).
3
+ *
4
+ * Authoring mode: web-component (ADR 0004 Mode 1). The canonical `component`
5
+ * field is the custom-element TAG NAME (`saacms-video`).
6
+ *
7
+ * `controls` defaults to true at render time (a video with no controls and no
8
+ * autoplay is a dead element). `autoplay` implies `muted` because browsers block
9
+ * unmuted autoplay — that coupling lives in the pure `resolveVideoAttrs` helper
10
+ * in Video.ts so it is unit-testable without a DOM.
11
+ */
12
+ import { Schema } from "effect";
13
+ declare const VideoSchema: Schema.Struct<{
14
+ src: typeof Schema.String;
15
+ poster: Schema.optional<typeof Schema.String>;
16
+ autoplay: Schema.optional<typeof Schema.Boolean>;
17
+ loop: Schema.optional<typeof Schema.Boolean>;
18
+ muted: Schema.optional<typeof Schema.Boolean>;
19
+ controls: Schema.optional<typeof Schema.Boolean>;
20
+ }>;
21
+ export type VideoProps = Schema.Schema.Type<typeof VideoSchema>;
22
+ export declare const VideoBlock: import("@saacms/core").BlockDef<Schema.Struct<{
23
+ src: typeof Schema.String;
24
+ poster: Schema.optional<typeof Schema.String>;
25
+ autoplay: Schema.optional<typeof Schema.Boolean>;
26
+ loop: Schema.optional<typeof Schema.Boolean>;
27
+ muted: Schema.optional<typeof Schema.Boolean>;
28
+ controls: Schema.optional<typeof Schema.Boolean>;
29
+ }>>;
30
+ export {};
31
+ //# sourceMappingURL=Video.block.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Video.block.d.ts","sourceRoot":"","sources":["../../src/video/Video.block.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAG/B,QAAA,MAAM,WAAW;;;;;;;EAOf,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,WAAW,CAAC,CAAA;AAE/D,eAAO,MAAM,UAAU;;;;;;;GAKrB,CAAA"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * <saacms-video> — built-in Video block (ADR 0004 Mode 1).
3
+ *
4
+ * Renders a single `<video>` into the light DOM, built via `createElement` +
5
+ * `setAttribute` (no `innerHTML` of user data). `src` and `poster` are passed
6
+ * through the shared `safeHref` allowlist (http(s)/relative only — `javascript:`
7
+ * and `data:` are neutralized; `data:` is irrelevant for video anyway).
8
+ *
9
+ * The final attribute set is computed by the pure, DOM-less `resolveVideoAttrs`
10
+ * helper so its rules are unit-testable:
11
+ * - `controls` defaults to `true` unless explicitly `false`.
12
+ * - `autoplay` FORCES `muted` true regardless of the `muted` prop, because
13
+ * browsers block unmuted autoplay (an unmuted-autoplay video just never
14
+ * plays). Coupling them here makes the emitted markup actually autoplay.
15
+ * - `playsinline` is always set and `preload` is always `"metadata"`.
16
+ */
17
+ import { HTMLElementBase } from "../internal/html-element-base.ts";
18
+ export { safeHref } from "../internal/safe-href.ts";
19
+ export interface VideoAttrInput {
20
+ readonly src?: string | null;
21
+ readonly poster?: string | null;
22
+ readonly autoplay?: boolean;
23
+ readonly loop?: boolean;
24
+ readonly muted?: boolean;
25
+ /** Omitted/undefined ⇒ defaults to true. Only an explicit `false` disables. */
26
+ readonly controls?: boolean;
27
+ }
28
+ export interface ResolvedVideoAttrs {
29
+ readonly src: string;
30
+ readonly poster: string;
31
+ readonly autoplay: boolean;
32
+ readonly loop: boolean;
33
+ readonly muted: boolean;
34
+ readonly controls: boolean;
35
+ readonly playsinline: true;
36
+ readonly preload: "metadata";
37
+ }
38
+ /**
39
+ * Pure resolver for the final `<video>` attribute set. DOM-less and total so the
40
+ * autoplay⇒muted coupling + controls default are unit-testable in isolation.
41
+ */
42
+ export declare function resolveVideoAttrs(input: VideoAttrInput): ResolvedVideoAttrs;
43
+ export declare class Video extends HTMLElementBase {
44
+ static get observedAttributes(): readonly string[];
45
+ get src(): string;
46
+ set src(value: string);
47
+ get poster(): string;
48
+ set poster(value: string);
49
+ connectedCallback(): void;
50
+ attributeChangedCallback(name: string, _oldValue: string | null, _newValue: string | null): void;
51
+ private resolved;
52
+ private render;
53
+ }
54
+ //# sourceMappingURL=Video.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Video.d.ts","sourceRoot":"","sources":["../../src/video/Video.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAA;AAGlE,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAYnD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAA;CAC7B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,kBAAkB,CAc3E;AAMD,qBAAa,KAAM,SAAQ,eAAe;IACxC,MAAM,KAAK,kBAAkB,IAAI,SAAS,MAAM,EAAE,CAEjD;IAGD,IAAI,GAAG,IAAI,MAAM,CAEhB;IACD,IAAI,GAAG,CAAC,KAAK,EAAE,MAAM,EAEpB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IACD,IAAI,MAAM,CAAC,KAAK,EAAE,MAAM,EAGvB;IAED,iBAAiB,IAAI,IAAI;IAIzB,wBAAwB,CACtB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,IAAI;IAKP,OAAO,CAAC,QAAQ;IAYhB,OAAO,CAAC,MAAM;CAiBf"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@saacms/blocks-essentials",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "import": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "default": "./dist/index.js"
10
+ }
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc --build",
18
+ "typecheck": "tsc --build --noEmit",
19
+ "prepack": "cp package.json package.json.pack-bak && bun run ../../scripts/prepack-pkg.ts",
20
+ "postpack": "mv package.json.pack-bak package.json"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "dependencies": {
26
+ "@saacms/core": "workspace:*",
27
+ "@preact/signals-core": "^1.8.0",
28
+ "effect": "^3.10.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/bun": "latest",
32
+ "typescript": "^5.7.0"
33
+ },
34
+ "main": "./dist/index.js",
35
+ "types": "./dist/index.d.ts"
36
+ }