@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
package/dist/index.js ADDED
@@ -0,0 +1,778 @@
1
+ // src/hero/Hero.ts
2
+ import { signal, effect } from "@preact/signals-core";
3
+
4
+ // src/internal/html-element-base.ts
5
+ var HTMLElementBase = typeof HTMLElement !== "undefined" ? HTMLElement : class {
6
+ };
7
+
8
+ // src/hero/Hero.ts
9
+ var OBSERVED_ATTRIBUTES = ["title", "subtitle", "cta-label", "cta-href"];
10
+
11
+ class Hero extends HTMLElementBase {
12
+ static get observedAttributes() {
13
+ return OBSERVED_ATTRIBUTES;
14
+ }
15
+ ctaHref = signal("");
16
+ disposeCtaEffect = null;
17
+ connectedCallback() {
18
+ this.ctaHref.value = this.getAttribute("cta-href") ?? "";
19
+ this.render();
20
+ this.disposeCtaEffect = effect(() => {
21
+ const anchor = this.querySelector("[data-cta-anchor]");
22
+ if (anchor !== null) {
23
+ anchor.href = this.ctaHref.value;
24
+ }
25
+ });
26
+ }
27
+ disconnectedCallback() {
28
+ if (this.disposeCtaEffect !== null) {
29
+ this.disposeCtaEffect();
30
+ this.disposeCtaEffect = null;
31
+ }
32
+ }
33
+ attributeChangedCallback(name, _oldValue, newValue) {
34
+ if (!isObserved(name))
35
+ return;
36
+ if (name === "cta-href") {
37
+ this.ctaHref.value = newValue ?? "";
38
+ return;
39
+ }
40
+ if (this.isConnected) {
41
+ this.render();
42
+ }
43
+ }
44
+ render() {
45
+ const title = escapeHtml(this.getAttribute("title") ?? "");
46
+ const subtitle = this.getAttribute("subtitle");
47
+ const ctaLabel = escapeHtml(this.getAttribute("cta-label") ?? "");
48
+ const subtitleMarkup = subtitle === null || subtitle.length === 0 ? "" : `<p class="saacms-hero__subtitle">${escapeHtml(subtitle)}</p>`;
49
+ const ctaMarkup = ctaLabel.length === 0 ? "" : `<a data-cta-anchor class="saacms-hero__cta" href="${escapeAttr(this.ctaHref.value)}">${ctaLabel}</a>`;
50
+ this.innerHTML = `
51
+ <section class="saacms-hero">
52
+ <h1 class="saacms-hero__title">${title}</h1>
53
+ ${subtitleMarkup}
54
+ ${ctaMarkup}
55
+ </section>
56
+ `.trim();
57
+ }
58
+ }
59
+ function isObserved(name) {
60
+ return OBSERVED_ATTRIBUTES.includes(name);
61
+ }
62
+ function escapeHtml(value) {
63
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
64
+ }
65
+ function escapeAttr(value) {
66
+ return escapeHtml(value);
67
+ }
68
+ // src/hero/Hero.block.ts
69
+ import { Schema } from "effect";
70
+ import { defineBlock } from "@saacms/core";
71
+ var HeroSchema = Schema.Struct({
72
+ title: Schema.String,
73
+ subtitle: Schema.optional(Schema.String),
74
+ ctaLabel: Schema.String,
75
+ ctaHref: Schema.String
76
+ });
77
+ var HeroBlock = defineBlock({
78
+ slug: "saacms-hero",
79
+ mode: "web-component",
80
+ schema: HeroSchema,
81
+ component: "saacms-hero"
82
+ });
83
+ // src/internal/sanitize-html.ts
84
+ var ALLOWED_TAGS = new Set([
85
+ "p",
86
+ "br",
87
+ "hr",
88
+ "span",
89
+ "div",
90
+ "strong",
91
+ "b",
92
+ "em",
93
+ "i",
94
+ "u",
95
+ "s",
96
+ "small",
97
+ "mark",
98
+ "sub",
99
+ "sup",
100
+ "a",
101
+ "ul",
102
+ "ol",
103
+ "li",
104
+ "blockquote",
105
+ "code",
106
+ "pre",
107
+ "h1",
108
+ "h2",
109
+ "h3",
110
+ "h4",
111
+ "h5",
112
+ "h6",
113
+ "table",
114
+ "thead",
115
+ "tbody",
116
+ "tr",
117
+ "td",
118
+ "th",
119
+ "img",
120
+ "figure",
121
+ "figcaption"
122
+ ]);
123
+ var ALLOWED_ATTRS = {
124
+ a: new Set(["href", "title"]),
125
+ img: new Set(["src", "alt", "title", "width", "height"])
126
+ };
127
+ var URL_ATTRS = new Set(["href", "src"]);
128
+ function hasDangerousScheme(value) {
129
+ const normalized = value.replace(/[\u0000-\u0020]+/g, "").toLowerCase();
130
+ return /^(javascript|vbscript|data|file):/.test(normalized);
131
+ }
132
+ var ATTR_RE = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*("[^"]*"|'[^']*'|[^\s"'>]+))?/g;
133
+ function sanitizeAttributes(tagName, rawAttrs) {
134
+ const allowed = ALLOWED_ATTRS[tagName];
135
+ if (allowed === undefined)
136
+ return "";
137
+ const kept = [];
138
+ let match;
139
+ ATTR_RE.lastIndex = 0;
140
+ while ((match = ATTR_RE.exec(rawAttrs)) !== null) {
141
+ const name = match[1].toLowerCase();
142
+ if (name.startsWith("on"))
143
+ continue;
144
+ if (!allowed.has(name))
145
+ continue;
146
+ const rawValue = match[2] ?? "";
147
+ const value = rawValue.length >= 2 && (rawValue.startsWith('"') || rawValue.startsWith("'")) ? rawValue.slice(1, -1) : rawValue;
148
+ if (URL_ATTRS.has(name) && hasDangerousScheme(value))
149
+ continue;
150
+ kept.push(`${name}="${value.replace(/"/g, "&quot;")}"`);
151
+ }
152
+ return kept.length === 0 ? "" : ` ${kept.join(" ")}`;
153
+ }
154
+ function sanitizeRichTextHtml(input) {
155
+ let html = input;
156
+ html = html.replace(/<\s*(script|style)\b[^>]*>[\s\S]*?(?:<\s*\/\s*\1\s*>|$)/gi, "");
157
+ html = html.replace(/<\s*\/?\s*(?:script|style)\b[^>]*>/gi, "");
158
+ html = html.replace(/<!--[\s\S]*?-->/g, "");
159
+ return html.replace(/<\s*(\/?)\s*([a-zA-Z][a-zA-Z0-9]*)((?:[^>"']|"[^"]*"|'[^']*')*)>/g, (_full, closing, name, rawAttrs) => {
160
+ const tag = name.toLowerCase();
161
+ if (!ALLOWED_TAGS.has(tag))
162
+ return "";
163
+ if (closing === "/")
164
+ return `</${tag}>`;
165
+ return `<${tag}${sanitizeAttributes(tag, rawAttrs)}>`;
166
+ });
167
+ }
168
+
169
+ // src/rich-text/RichText.ts
170
+ var OBSERVED_ATTRIBUTES2 = ["html", "align"];
171
+ function resolveAlign(value) {
172
+ return value === "center" || value === "right" ? value : "left";
173
+ }
174
+
175
+ class RichText extends HTMLElementBase {
176
+ static get observedAttributes() {
177
+ return OBSERVED_ATTRIBUTES2;
178
+ }
179
+ get html() {
180
+ return this.getAttribute("html") ?? "";
181
+ }
182
+ set html(value) {
183
+ this.setAttribute("html", value ?? "");
184
+ }
185
+ get align() {
186
+ return resolveAlign(this.getAttribute("align"));
187
+ }
188
+ set align(value) {
189
+ if (value === null) {
190
+ this.removeAttribute("align");
191
+ } else {
192
+ this.setAttribute("align", value);
193
+ }
194
+ }
195
+ connectedCallback() {
196
+ this.render();
197
+ }
198
+ attributeChangedCallback(name, _oldValue, _newValue) {
199
+ if (!isObserved2(name))
200
+ return;
201
+ if (this.isConnected) {
202
+ this.render();
203
+ }
204
+ }
205
+ render() {
206
+ const align = resolveAlign(this.getAttribute("align"));
207
+ const safe = sanitizeRichTextHtml(this.getAttribute("html") ?? "");
208
+ this.innerHTML = `<div class="saacms-rich-text" style="text-align:${align}">${safe}</div>`;
209
+ }
210
+ }
211
+ function isObserved2(name) {
212
+ return OBSERVED_ATTRIBUTES2.includes(name);
213
+ }
214
+ // src/rich-text/RichText.block.ts
215
+ import { Schema as Schema2 } from "effect";
216
+ import { defineBlock as defineBlock2 } from "@saacms/core";
217
+ var RichTextSchema = Schema2.Struct({
218
+ html: Schema2.String,
219
+ align: Schema2.optional(Schema2.Literal("left", "center", "right"))
220
+ });
221
+ var RichTextBlock = defineBlock2({
222
+ slug: "rich-text",
223
+ mode: "web-component",
224
+ schema: RichTextSchema,
225
+ component: "saacms-rich-text"
226
+ });
227
+ // src/image/Image.ts
228
+ var OBSERVED_ATTRIBUTES3 = ["src", "alt", "width", "height", "loading"];
229
+ function escapeAttr2(value) {
230
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
231
+ }
232
+ function resolveLoading(value) {
233
+ return value === "eager" ? "eager" : "lazy";
234
+ }
235
+ function dimensionAttr(name, raw) {
236
+ if (raw === null)
237
+ return "";
238
+ const n = Number(raw);
239
+ return Number.isFinite(n) && n > 0 ? ` ${name}="${Math.floor(n)}"` : "";
240
+ }
241
+
242
+ class Image extends HTMLElementBase {
243
+ static get observedAttributes() {
244
+ return OBSERVED_ATTRIBUTES3;
245
+ }
246
+ get src() {
247
+ return this.getAttribute("src") ?? "";
248
+ }
249
+ set src(value) {
250
+ this.setAttribute("src", value ?? "");
251
+ }
252
+ get alt() {
253
+ return this.getAttribute("alt") ?? "";
254
+ }
255
+ set alt(value) {
256
+ this.setAttribute("alt", value ?? "");
257
+ }
258
+ get width() {
259
+ const raw = this.getAttribute("width");
260
+ return raw === null ? null : Number(raw);
261
+ }
262
+ set width(value) {
263
+ if (value === null)
264
+ this.removeAttribute("width");
265
+ else
266
+ this.setAttribute("width", String(value));
267
+ }
268
+ get height() {
269
+ const raw = this.getAttribute("height");
270
+ return raw === null ? null : Number(raw);
271
+ }
272
+ set height(value) {
273
+ if (value === null)
274
+ this.removeAttribute("height");
275
+ else
276
+ this.setAttribute("height", String(value));
277
+ }
278
+ get loading() {
279
+ return resolveLoading(this.getAttribute("loading"));
280
+ }
281
+ set loading(value) {
282
+ if (value === null)
283
+ this.removeAttribute("loading");
284
+ else
285
+ this.setAttribute("loading", value);
286
+ }
287
+ connectedCallback() {
288
+ this.render();
289
+ }
290
+ attributeChangedCallback(name, _oldValue, _newValue) {
291
+ if (!isObserved3(name))
292
+ return;
293
+ if (this.isConnected) {
294
+ this.render();
295
+ }
296
+ }
297
+ render() {
298
+ const src = this.getAttribute("src") ?? "";
299
+ const alt = this.getAttribute("alt");
300
+ const hasAlt = alt !== null && alt.trim().length > 0;
301
+ const loading = resolveLoading(this.getAttribute("loading"));
302
+ const parts = [
303
+ `src="${escapeAttr2(src)}"`,
304
+ `alt="${escapeAttr2(alt ?? "")}"`,
305
+ `loading="${loading}"`,
306
+ `decoding="async"`
307
+ ];
308
+ const width = dimensionAttr("width", this.getAttribute("width")).trim();
309
+ if (width.length > 0)
310
+ parts.push(width);
311
+ const height = dimensionAttr("height", this.getAttribute("height")).trim();
312
+ if (height.length > 0)
313
+ parts.push(height);
314
+ if (!hasAlt)
315
+ parts.push(`data-saacms-warn="missing-alt"`);
316
+ this.innerHTML = `<img class="saacms-image" ${parts.join(" ")}>`;
317
+ }
318
+ }
319
+ function isObserved3(name) {
320
+ return OBSERVED_ATTRIBUTES3.includes(name);
321
+ }
322
+ // src/image/Image.block.ts
323
+ import { Schema as Schema3 } from "effect";
324
+ import { defineBlock as defineBlock3 } from "@saacms/core";
325
+ var ImageSchema = Schema3.Struct({
326
+ src: Schema3.String,
327
+ alt: Schema3.String,
328
+ width: Schema3.optional(Schema3.Number),
329
+ height: Schema3.optional(Schema3.Number),
330
+ loading: Schema3.optional(Schema3.Literal("lazy", "eager"))
331
+ });
332
+ var ImageBlock = defineBlock3({
333
+ slug: "image",
334
+ mode: "web-component",
335
+ schema: ImageSchema,
336
+ component: "saacms-image"
337
+ });
338
+ // src/code-block/CodeBlock.ts
339
+ var OBSERVED_ATTRIBUTES4 = ["code", "lang", "show-line-numbers"];
340
+ function escapeCode(value) {
341
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
342
+ }
343
+ function resolveBool(attr) {
344
+ return attr !== null && attr !== "false";
345
+ }
346
+
347
+ class CodeBlock extends HTMLElementBase {
348
+ static get observedAttributes() {
349
+ return OBSERVED_ATTRIBUTES4;
350
+ }
351
+ get code() {
352
+ return this.getAttribute("code") ?? "";
353
+ }
354
+ set code(value) {
355
+ this.setAttribute("code", value ?? "");
356
+ }
357
+ get lang() {
358
+ return this.getAttribute("lang") ?? "";
359
+ }
360
+ set lang(value) {
361
+ if (value === "")
362
+ this.removeAttribute("lang");
363
+ else
364
+ this.setAttribute("lang", value);
365
+ }
366
+ get showLineNumbers() {
367
+ return resolveBool(this.getAttribute("show-line-numbers"));
368
+ }
369
+ set showLineNumbers(value) {
370
+ if (value)
371
+ this.setAttribute("show-line-numbers", "true");
372
+ else
373
+ this.removeAttribute("show-line-numbers");
374
+ }
375
+ connectedCallback() {
376
+ this.render();
377
+ }
378
+ attributeChangedCallback(name, _oldValue, _newValue) {
379
+ if (!isObserved4(name))
380
+ return;
381
+ if (this.isConnected) {
382
+ this.render();
383
+ }
384
+ }
385
+ render() {
386
+ if (typeof document === "undefined")
387
+ return;
388
+ const lang = this.getAttribute("lang");
389
+ const code = this.getAttribute("code") ?? "";
390
+ const showLineNumbers = resolveBool(this.getAttribute("show-line-numbers"));
391
+ const pre = document.createElement("pre");
392
+ pre.className = "saacms-code-block";
393
+ const codeEl = document.createElement("code");
394
+ codeEl.className = lang !== null && lang.length > 0 ? `language-${lang}` : "language-plaintext";
395
+ if (showLineNumbers) {
396
+ const lines = code.split(`
397
+ `);
398
+ lines.forEach((line, index) => {
399
+ const span = document.createElement("span");
400
+ span.className = "saacms-line";
401
+ span.textContent = line;
402
+ codeEl.appendChild(span);
403
+ if (index < lines.length - 1) {
404
+ codeEl.appendChild(document.createTextNode(`
405
+ `));
406
+ }
407
+ });
408
+ } else {
409
+ codeEl.textContent = code;
410
+ }
411
+ pre.appendChild(codeEl);
412
+ this.replaceChildren(pre);
413
+ }
414
+ }
415
+ function isObserved4(name) {
416
+ return OBSERVED_ATTRIBUTES4.includes(name);
417
+ }
418
+ // src/code-block/CodeBlock.block.ts
419
+ import { Schema as Schema4 } from "effect";
420
+ import { defineBlock as defineBlock4 } from "@saacms/core";
421
+ var CodeBlockSchema = Schema4.Struct({
422
+ code: Schema4.String,
423
+ lang: Schema4.optional(Schema4.String),
424
+ showLineNumbers: Schema4.optional(Schema4.Boolean)
425
+ });
426
+ var CodeBlockBlock = defineBlock4({
427
+ slug: "code-block",
428
+ mode: "web-component",
429
+ schema: CodeBlockSchema,
430
+ component: "saacms-code-block"
431
+ });
432
+ // src/internal/safe-href.ts
433
+ function safeHref(value) {
434
+ const trimmed = value.trim();
435
+ if (trimmed === "")
436
+ return "";
437
+ const normalized = trimmed.replace(/[\u0000-\u0020]+/g, "").toLowerCase();
438
+ const schemeMatch = normalized.match(/^([a-z][a-z0-9+.-]*):/);
439
+ if (schemeMatch === null) {
440
+ return trimmed;
441
+ }
442
+ const scheme = schemeMatch[1];
443
+ return scheme === "http" || scheme === "https" ? trimmed : "";
444
+ }
445
+
446
+ // src/pricing-table/PricingTable.ts
447
+ var OBSERVED_ATTRIBUTES5 = ["tiers"];
448
+ function asString(value) {
449
+ return typeof value === "string" ? value : "";
450
+ }
451
+ function coerceTiers(value) {
452
+ if (!Array.isArray(value))
453
+ return [];
454
+ return value.map((raw) => {
455
+ const t = raw ?? {};
456
+ const features = Array.isArray(t.features) ? t.features.map(asString) : [];
457
+ return {
458
+ name: asString(t.name),
459
+ price: asString(t.price),
460
+ features,
461
+ highlighted: t.highlighted === true,
462
+ ctaLabel: typeof t.ctaLabel === "string" ? t.ctaLabel : undefined,
463
+ ctaHref: typeof t.ctaHref === "string" ? t.ctaHref : undefined
464
+ };
465
+ });
466
+ }
467
+
468
+ class PricingTable extends HTMLElementBase {
469
+ static get observedAttributes() {
470
+ return OBSERVED_ATTRIBUTES5;
471
+ }
472
+ propTiers = null;
473
+ get tiers() {
474
+ if (this.propTiers !== null)
475
+ return this.propTiers;
476
+ return coerceTiers(this.parseAttr());
477
+ }
478
+ set tiers(value) {
479
+ this.propTiers = value === null ? null : coerceTiers(value);
480
+ if (this.isConnected)
481
+ this.render();
482
+ }
483
+ parseAttr() {
484
+ const raw = this.getAttribute("tiers");
485
+ if (raw === null || raw.trim() === "")
486
+ return [];
487
+ try {
488
+ return JSON.parse(raw);
489
+ } catch {
490
+ return [];
491
+ }
492
+ }
493
+ connectedCallback() {
494
+ this.render();
495
+ }
496
+ attributeChangedCallback(name, _oldValue, _newValue) {
497
+ if (!isObserved5(name))
498
+ return;
499
+ this.propTiers = null;
500
+ if (this.isConnected)
501
+ this.render();
502
+ }
503
+ render() {
504
+ if (typeof document === "undefined")
505
+ return;
506
+ const tiers = this.propTiers !== null ? this.propTiers : coerceTiers(this.parseAttr());
507
+ const root = document.createElement("div");
508
+ root.className = "saacms-pricing-table";
509
+ for (const tier of tiers) {
510
+ const card = document.createElement("div");
511
+ card.className = "saacms-tier";
512
+ card.setAttribute("data-highlighted", tier.highlighted === true ? "true" : "false");
513
+ const name = document.createElement("h3");
514
+ name.className = "saacms-tier__name";
515
+ name.textContent = tier.name;
516
+ card.appendChild(name);
517
+ const price = document.createElement("p");
518
+ price.className = "saacms-tier__price";
519
+ price.textContent = tier.price;
520
+ card.appendChild(price);
521
+ const list = document.createElement("ul");
522
+ list.className = "saacms-tier__features";
523
+ for (const feature of tier.features) {
524
+ const li = document.createElement("li");
525
+ li.textContent = feature;
526
+ list.appendChild(li);
527
+ }
528
+ card.appendChild(list);
529
+ if (tier.ctaLabel !== undefined && tier.ctaLabel.length > 0) {
530
+ const href = safeHref(tier.ctaHref ?? "");
531
+ const cta = document.createElement("a");
532
+ cta.className = "saacms-tier__cta";
533
+ cta.textContent = tier.ctaLabel;
534
+ if (href !== "")
535
+ cta.setAttribute("href", href);
536
+ card.appendChild(cta);
537
+ }
538
+ root.appendChild(card);
539
+ }
540
+ this.replaceChildren(root);
541
+ }
542
+ }
543
+ function isObserved5(name) {
544
+ return OBSERVED_ATTRIBUTES5.includes(name);
545
+ }
546
+ // src/pricing-table/PricingTable.block.ts
547
+ import { Schema as Schema5 } from "effect";
548
+ import { defineBlock as defineBlock5 } from "@saacms/core";
549
+ var PricingTableSchema = Schema5.Struct({
550
+ tiers: Schema5.Array(Schema5.Struct({
551
+ name: Schema5.String,
552
+ price: Schema5.String,
553
+ features: Schema5.Array(Schema5.String),
554
+ highlighted: Schema5.optional(Schema5.Boolean),
555
+ ctaLabel: Schema5.optional(Schema5.String),
556
+ ctaHref: Schema5.optional(Schema5.String)
557
+ }))
558
+ });
559
+ var PricingTableBlock = defineBlock5({
560
+ slug: "pricing-table",
561
+ mode: "web-component",
562
+ schema: PricingTableSchema,
563
+ component: "saacms-pricing-table"
564
+ });
565
+ // src/video/Video.ts
566
+ var OBSERVED_ATTRIBUTES6 = [
567
+ "src",
568
+ "poster",
569
+ "autoplay",
570
+ "loop",
571
+ "muted",
572
+ "controls"
573
+ ];
574
+ function resolveVideoAttrs(input) {
575
+ const autoplay = input.autoplay === true;
576
+ return {
577
+ src: safeHref(input.src ?? ""),
578
+ poster: safeHref(input.poster ?? ""),
579
+ autoplay,
580
+ loop: input.loop === true,
581
+ muted: autoplay ? true : input.muted === true,
582
+ controls: input.controls !== false,
583
+ playsinline: true,
584
+ preload: "metadata"
585
+ };
586
+ }
587
+ function boolAttr(attr) {
588
+ return attr !== null && attr !== "false";
589
+ }
590
+
591
+ class Video extends HTMLElementBase {
592
+ static get observedAttributes() {
593
+ return OBSERVED_ATTRIBUTES6;
594
+ }
595
+ get src() {
596
+ return this.getAttribute("src") ?? "";
597
+ }
598
+ set src(value) {
599
+ this.setAttribute("src", value ?? "");
600
+ }
601
+ get poster() {
602
+ return this.getAttribute("poster") ?? "";
603
+ }
604
+ set poster(value) {
605
+ if (value === "")
606
+ this.removeAttribute("poster");
607
+ else
608
+ this.setAttribute("poster", value);
609
+ }
610
+ connectedCallback() {
611
+ this.render();
612
+ }
613
+ attributeChangedCallback(name, _oldValue, _newValue) {
614
+ if (!isObserved6(name))
615
+ return;
616
+ if (this.isConnected)
617
+ this.render();
618
+ }
619
+ resolved() {
620
+ const controlsAttr = this.getAttribute("controls");
621
+ return resolveVideoAttrs({
622
+ src: this.getAttribute("src"),
623
+ poster: this.getAttribute("poster"),
624
+ autoplay: boolAttr(this.getAttribute("autoplay")),
625
+ loop: boolAttr(this.getAttribute("loop")),
626
+ muted: boolAttr(this.getAttribute("muted")),
627
+ controls: controlsAttr === null ? undefined : controlsAttr !== "false"
628
+ });
629
+ }
630
+ render() {
631
+ if (typeof document === "undefined")
632
+ return;
633
+ const a = this.resolved();
634
+ const video = document.createElement("video");
635
+ video.className = "saacms-video";
636
+ if (a.src !== "")
637
+ video.setAttribute("src", a.src);
638
+ if (a.poster !== "")
639
+ video.setAttribute("poster", a.poster);
640
+ if (a.autoplay)
641
+ video.setAttribute("autoplay", "");
642
+ if (a.loop)
643
+ video.setAttribute("loop", "");
644
+ if (a.muted)
645
+ video.setAttribute("muted", "");
646
+ if (a.controls)
647
+ video.setAttribute("controls", "");
648
+ video.setAttribute("playsinline", "");
649
+ video.setAttribute("preload", a.preload);
650
+ this.replaceChildren(video);
651
+ }
652
+ }
653
+ function isObserved6(name) {
654
+ return OBSERVED_ATTRIBUTES6.includes(name);
655
+ }
656
+ // src/video/Video.block.ts
657
+ import { Schema as Schema6 } from "effect";
658
+ import { defineBlock as defineBlock6 } from "@saacms/core";
659
+ var VideoSchema = Schema6.Struct({
660
+ src: Schema6.String,
661
+ poster: Schema6.optional(Schema6.String),
662
+ autoplay: Schema6.optional(Schema6.Boolean),
663
+ loop: Schema6.optional(Schema6.Boolean),
664
+ muted: Schema6.optional(Schema6.Boolean),
665
+ controls: Schema6.optional(Schema6.Boolean)
666
+ });
667
+ var VideoBlock = defineBlock6({
668
+ slug: "video",
669
+ mode: "web-component",
670
+ schema: VideoSchema,
671
+ component: "saacms-video"
672
+ });
673
+ // src/raw-html/RawHtml.ts
674
+ var OBSERVED_ATTRIBUTES7 = ["html", "trusted"];
675
+ var TRUSTED_WARNING = "[saacms/blocks] saacms-raw-html trusted=true bypasses sanitization — only use for first-party content";
676
+
677
+ class RawHtml extends HTMLElementBase {
678
+ static get observedAttributes() {
679
+ return OBSERVED_ATTRIBUTES7;
680
+ }
681
+ warnedTrusted = false;
682
+ get html() {
683
+ return this.getAttribute("html") ?? "";
684
+ }
685
+ set html(value) {
686
+ this.setAttribute("html", value ?? "");
687
+ }
688
+ get trusted() {
689
+ return this.getAttribute("trusted") === "true";
690
+ }
691
+ set trusted(value) {
692
+ if (value)
693
+ this.setAttribute("trusted", "true");
694
+ else
695
+ this.removeAttribute("trusted");
696
+ }
697
+ connectedCallback() {
698
+ this.render();
699
+ }
700
+ attributeChangedCallback(name, _oldValue, _newValue) {
701
+ if (!isObserved7(name))
702
+ return;
703
+ if (this.isConnected)
704
+ this.render();
705
+ }
706
+ render() {
707
+ const html = this.getAttribute("html") ?? "";
708
+ const trusted = this.getAttribute("trusted") === "true";
709
+ if (trusted) {
710
+ this.setAttribute("data-saacms-trusted", "true");
711
+ if (!this.warnedTrusted) {
712
+ this.warnedTrusted = true;
713
+ console.warn(TRUSTED_WARNING);
714
+ }
715
+ this.innerHTML = html;
716
+ return;
717
+ }
718
+ this.removeAttribute("data-saacms-trusted");
719
+ this.innerHTML = sanitizeRichTextHtml(html);
720
+ }
721
+ }
722
+ function isObserved7(name) {
723
+ return OBSERVED_ATTRIBUTES7.includes(name);
724
+ }
725
+ // src/raw-html/RawHtml.block.ts
726
+ import { Schema as Schema7 } from "effect";
727
+ import { defineBlock as defineBlock7 } from "@saacms/core";
728
+ var RawHtmlSchema = Schema7.Struct({
729
+ html: Schema7.String,
730
+ trusted: Schema7.optional(Schema7.Boolean)
731
+ });
732
+ var RawHtmlBlock = defineBlock7({
733
+ slug: "raw-html",
734
+ mode: "web-component",
735
+ schema: RawHtmlSchema,
736
+ component: "saacms-raw-html"
737
+ });
738
+ // src/register.ts
739
+ var REGISTRATIONS = [
740
+ { tag: "saacms-hero", ctor: Hero },
741
+ { tag: "saacms-rich-text", ctor: RichText },
742
+ { tag: "saacms-image", ctor: Image },
743
+ { tag: "saacms-code-block", ctor: CodeBlock },
744
+ { tag: "saacms-pricing-table", ctor: PricingTable },
745
+ { tag: "saacms-video", ctor: Video },
746
+ { tag: "saacms-raw-html", ctor: RawHtml }
747
+ ];
748
+ function register() {
749
+ if (typeof customElements === "undefined")
750
+ return;
751
+ for (const { tag, ctor } of REGISTRATIONS) {
752
+ if (customElements.get(tag) === undefined) {
753
+ customElements.define(tag, ctor);
754
+ }
755
+ }
756
+ }
757
+ export {
758
+ sanitizeRichTextHtml,
759
+ safeHref,
760
+ resolveVideoAttrs,
761
+ register,
762
+ escapeCode,
763
+ VideoBlock,
764
+ Video,
765
+ RichTextBlock,
766
+ RichText,
767
+ RawHtmlBlock,
768
+ RawHtml,
769
+ REGISTRATIONS,
770
+ PricingTableBlock,
771
+ PricingTable,
772
+ ImageBlock,
773
+ Image,
774
+ HeroBlock,
775
+ Hero,
776
+ CodeBlockBlock,
777
+ CodeBlock
778
+ };