@ptengine/lp-editor-core 1.0.0-alpha.2 → 1.0.0-alpha.4
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/README.md +28 -3
- package/dist/golden/component-cases.json +550 -0
- package/dist/golden/fixtures/page/01-etvos-article-lp.html +1 -1
- package/dist/golden/fixtures/page/02-vivaia-campaign-lp.html +1 -1
- package/dist/golden/fixtures/popup/14-back-to-school-email-form.html +1 -1
- package/dist/golden/fixtures/popup/15-xtool-ebook-split-form.html +1 -1
- package/dist/golden/fixtures/popup/16-880-off-gradient-form.html +1 -1
- package/dist/golden/fixtures/popup/README.md +9 -4
- package/dist/golden/manifest.json +421 -6
- package/dist/golden/verify.mjs +36 -2
- package/dist/index.d.ts +585 -246
- package/dist/index.js +1047 -83
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -121,9 +121,9 @@ function canonicalTagName(el) {
|
|
|
121
121
|
}
|
|
122
122
|
function sortedAttributes(el) {
|
|
123
123
|
const attrs = [];
|
|
124
|
-
const
|
|
125
|
-
for (let i = 0; i <
|
|
126
|
-
const a =
|
|
124
|
+
const list2 = el.attributes;
|
|
125
|
+
for (let i = 0; i < list2.length; i++) {
|
|
126
|
+
const a = list2[i];
|
|
127
127
|
const raw = a.value === null || a.value === void 0 ? "" : a.value;
|
|
128
128
|
attrs.push([a.name, a.name === "class" ? normalizeClassValue(raw) : raw]);
|
|
129
129
|
}
|
|
@@ -251,6 +251,9 @@ var THEME_PT_ID = "theme";
|
|
|
251
251
|
var ROOT_PT_ID = "root";
|
|
252
252
|
var FORMAT_META_NAME = "lpx-format";
|
|
253
253
|
var FORMAT_VERSION = "4";
|
|
254
|
+
var WRAP_ATTR = "data-pt-wrap";
|
|
255
|
+
var WRAP_SECTION_PT_ID = "ptwrap-section";
|
|
256
|
+
var WRAP_CONTAINER_PT_ID = "ptwrap-container";
|
|
254
257
|
var SWITCHES = [
|
|
255
258
|
{
|
|
256
259
|
key: "hide",
|
|
@@ -352,6 +355,9 @@ var rules = {
|
|
|
352
355
|
rootPtId: ROOT_PT_ID,
|
|
353
356
|
formatMetaName: FORMAT_META_NAME,
|
|
354
357
|
formatVersion: FORMAT_VERSION,
|
|
358
|
+
wrapAttr: WRAP_ATTR,
|
|
359
|
+
wrapSectionPtId: WRAP_SECTION_PT_ID,
|
|
360
|
+
wrapContainerPtId: WRAP_CONTAINER_PT_ID,
|
|
355
361
|
switches: SWITCHES,
|
|
356
362
|
crossDeviceConsistentProperties: CROSS_DEVICE_CONSISTENT_PROPERTIES,
|
|
357
363
|
opaqueModes: OPAQUE_MODES,
|
|
@@ -362,44 +368,9 @@ var rules = {
|
|
|
362
368
|
arbitraryValueAllowedProperties: ARBITRARY_VALUE_ALLOWED_PROPERTIES
|
|
363
369
|
};
|
|
364
370
|
|
|
365
|
-
// src/kinds.ts
|
|
366
|
-
var KIND_PROFILES = {
|
|
367
|
-
page: {
|
|
368
|
-
root: "document",
|
|
369
|
-
layers: ["section", "container", "leaf"],
|
|
370
|
-
compile: "document",
|
|
371
|
-
leafTypes: ["text", "media", "button", "shape"],
|
|
372
|
-
components: {
|
|
373
|
-
carousel: "self",
|
|
374
|
-
faq: "self",
|
|
375
|
-
tabs: "self",
|
|
376
|
-
"sticky-cta": "self",
|
|
377
|
-
form: "self"
|
|
378
|
-
},
|
|
379
|
-
units: { vh: true },
|
|
380
|
-
customCode: true
|
|
381
|
-
},
|
|
382
|
-
popup: {
|
|
383
|
-
root: "fragment",
|
|
384
|
-
// 弹窗无 section 层:root › children。
|
|
385
|
-
layers: ["container", "leaf"],
|
|
386
|
-
compile: "shadow-px",
|
|
387
|
-
leafTypes: ["text", "media", "button", "shape"],
|
|
388
|
-
components: {
|
|
389
|
-
form: "engage-sdk",
|
|
390
|
-
countdown: "engage-sdk",
|
|
391
|
-
carousel: "self",
|
|
392
|
-
faq: "self",
|
|
393
|
-
tabs: "self"
|
|
394
|
-
},
|
|
395
|
-
units: { vh: false },
|
|
396
|
-
customCode: false
|
|
397
|
-
}
|
|
398
|
-
};
|
|
399
|
-
|
|
400
371
|
// src/version.ts
|
|
401
|
-
var VERSION = "1.0.0-alpha.
|
|
402
|
-
var COMPILER_VERSION = "
|
|
372
|
+
var VERSION = "1.0.0-alpha.4";
|
|
373
|
+
var COMPILER_VERSION = "2";
|
|
403
374
|
|
|
404
375
|
// src/validate/tree.ts
|
|
405
376
|
var INLINE_SET = new Set(INLINE_NO_ID_TAGS);
|
|
@@ -560,8 +531,165 @@ function nextImageSibling(from) {
|
|
|
560
531
|
}
|
|
561
532
|
function* walkElements(root) {
|
|
562
533
|
if ("tagName" in root) yield root;
|
|
563
|
-
const
|
|
564
|
-
for (let i = 0; i <
|
|
534
|
+
const list2 = root.querySelectorAll("*");
|
|
535
|
+
for (let i = 0; i < list2.length; i++) yield list2[i];
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// src/wrap.ts
|
|
539
|
+
function isWrapLayer(el) {
|
|
540
|
+
return el.hasAttribute(WRAP_ATTR);
|
|
541
|
+
}
|
|
542
|
+
var SKELETON = `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="${FORMAT_META_NAME}" content="${FORMAT_VERSION}"></head><body></body></html>`;
|
|
543
|
+
function createWrap(parse) {
|
|
544
|
+
return function wrap(fragmentHtml) {
|
|
545
|
+
if (typeof fragmentHtml !== "string") {
|
|
546
|
+
throw new TypeError("wrap(): fragmentHtml \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
547
|
+
}
|
|
548
|
+
const doc = parse(SKELETON);
|
|
549
|
+
const frag = parseFragment(doc, fragmentHtml);
|
|
550
|
+
const { theme, root, before, after } = checkFragmentShape(frag);
|
|
551
|
+
checkReservedLiterals(frag, root, theme);
|
|
552
|
+
const body = doc.body;
|
|
553
|
+
const attrs = root.attributes;
|
|
554
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
555
|
+
body.setAttribute(attrs[i].name, attrs[i].value);
|
|
556
|
+
}
|
|
557
|
+
if (theme) doc.head.appendChild(theme);
|
|
558
|
+
const section = doc.createElement("section");
|
|
559
|
+
section.setAttribute(WRAP_ATTR, "");
|
|
560
|
+
section.setAttribute("data-pt-id", WRAP_SECTION_PT_ID);
|
|
561
|
+
const container = doc.createElement("div");
|
|
562
|
+
container.setAttribute(WRAP_ATTR, "");
|
|
563
|
+
container.setAttribute("data-pt-id", WRAP_CONTAINER_PT_ID);
|
|
564
|
+
while (root.firstChild) container.appendChild(root.firstChild);
|
|
565
|
+
section.appendChild(container);
|
|
566
|
+
for (const node of before) body.appendChild(node);
|
|
567
|
+
body.appendChild(section);
|
|
568
|
+
for (const node of after) body.appendChild(node);
|
|
569
|
+
return doc;
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
function checkFragmentShape(frag) {
|
|
573
|
+
const elements = [];
|
|
574
|
+
const nonElements = [];
|
|
575
|
+
let child = frag.firstChild;
|
|
576
|
+
while (child) {
|
|
577
|
+
const next = child.nextSibling;
|
|
578
|
+
if (child.nodeType === 1) {
|
|
579
|
+
elements.push(child);
|
|
580
|
+
} else if (child.nodeType === 3 && child.data.trim() === "") {
|
|
581
|
+
nonElements.push({ node: child, afterElements: elements.length });
|
|
582
|
+
} else {
|
|
583
|
+
throw new TypeError(
|
|
584
|
+
`wrap(): \u7247\u6BB5\u9876\u5C42\u53EA\u5141\u8BB8 theme \u5757\u3001\u5355\u4E00\u6839\u5143\u7D20\u4E0E\u7A7A\u767D \u2014\u2014 \u51FA\u73B0\u4E86 ${describeNode(child)}\u3002\u91CE\u751F\u5F62\u72B6\u8BF7\u5728\u8C03\u7528\u65B9\u7684 adapter \u91CC\u5148\u89C4\u6574\u3002`
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
child = next;
|
|
588
|
+
}
|
|
589
|
+
let theme = null;
|
|
590
|
+
let root;
|
|
591
|
+
if (elements.length === 2) {
|
|
592
|
+
if (!isThemeStyle(elements[0])) {
|
|
593
|
+
throw new TypeError(
|
|
594
|
+
'wrap(): \u7247\u6BB5\u9876\u5C42\u6709\u4E24\u4E2A\u5143\u7D20\u65F6\uFF0C\u7B2C\u4E00\u4E2A\u5FC5\u987B\u662F\u4E3B\u9898\u5757 <style type="text/tailwindcss">'
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
theme = elements[0];
|
|
598
|
+
root = elements[1];
|
|
599
|
+
} else if (elements.length === 1) {
|
|
600
|
+
if (isThemeStyle(elements[0])) {
|
|
601
|
+
throw new TypeError("wrap(): \u7247\u6BB5\u53EA\u6709\u4E3B\u9898\u5757\u3001\u6CA1\u6709\u6839\u5143\u7D20");
|
|
602
|
+
}
|
|
603
|
+
root = elements[0];
|
|
604
|
+
} else {
|
|
605
|
+
throw new TypeError(
|
|
606
|
+
`wrap(): \u7247\u6BB5\u9876\u5C42\u5FC5\u987B\u6070\u6709\u4E00\u4E2A\u6839\u5143\u7D20\uFF08\u53EF\u9009\u4E00\u4E2A\u4E3B\u9898\u5757\uFF09\uFF0C\u5B9E\u9645\u6709 ${elements.length} \u4E2A\u5143\u7D20`
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
if (tag(root) !== "div") {
|
|
610
|
+
throw new TypeError(`wrap(): \u7247\u6BB5\u6839\u5143\u7D20\u5FC5\u987B\u662F <div>\uFF0C\u5B9E\u9645\u662F <${tag(root)}>`);
|
|
611
|
+
}
|
|
612
|
+
if (root.getAttribute("data-pt-id") !== ROOT_PT_ID) {
|
|
613
|
+
throw new TypeError(
|
|
614
|
+
`wrap(): \u7247\u6BB5\u6839\u5143\u7D20\u7684 data-pt-id \u5FC5\u987B\u662F ${ROOT_PT_ID}\uFF0C\u5B9E\u9645\u662F ${JSON.stringify(root.getAttribute("data-pt-id"))}\u3002\u5B83\u4F1A\u88AB\u8BA4\u9886\u6210\u6587\u6863\u7684 <body>\uFF0C\u6539\u540D\u4F1A\u8BA9\u57CB\u70B9\u951A\u70B9\u65AD\u94FE\u3002`
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
const rootIndex = elements.indexOf(root);
|
|
618
|
+
const before = [];
|
|
619
|
+
const after = [];
|
|
620
|
+
for (const { node, afterElements } of nonElements) {
|
|
621
|
+
if (afterElements <= rootIndex) before.push(node);
|
|
622
|
+
else after.push(node);
|
|
623
|
+
}
|
|
624
|
+
return { theme, root, before, after };
|
|
625
|
+
}
|
|
626
|
+
function checkReservedLiterals(frag, root, theme) {
|
|
627
|
+
const all = frag.querySelectorAll("*");
|
|
628
|
+
for (let i = 0; i < all.length; i++) {
|
|
629
|
+
const el = all[i];
|
|
630
|
+
if (el.hasAttribute(WRAP_ATTR)) {
|
|
631
|
+
throw new TypeError(
|
|
632
|
+
`wrap(): \u7247\u6BB5\u91CC\u5DF2\u7ECF\u5E26\u7740\u5408\u6210\u5C42\u6807\u8BB0 ${WRAP_ATTR} \u2014\u2014 \u591A\u534A\u662F\u4E0A\u4E00\u8F6E\u6F0F\u5265\u6216\u56DE\u704C\uFF0C\u5148\u67E5\u4E0A\u6E38\uFF0C\u4E0D\u8981\u91CD\u590D\u5305\u88F9\u3002`
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
const id = el.getAttribute("data-pt-id");
|
|
636
|
+
if (id === null) continue;
|
|
637
|
+
if (id === WRAP_SECTION_PT_ID || id === WRAP_CONTAINER_PT_ID) {
|
|
638
|
+
throw new TypeError(`wrap(): \u7247\u6BB5\u91CC\u5DF2\u7ECF\u5360\u7528\u4E86\u5408\u6210\u5C42\u7684\u4FDD\u7559 id ${id}`);
|
|
639
|
+
}
|
|
640
|
+
if (id === ROOT_PT_ID && el !== root) {
|
|
641
|
+
throw new TypeError(
|
|
642
|
+
`wrap(): \u4FDD\u7559 id ${ROOT_PT_ID} \u53EA\u8BB8\u51FA\u73B0\u5728\u7247\u6BB5\u6839\u5143\u7D20\u4E0A\uFF0C\u8FD9\u91CC\u51FA\u73B0\u5728 <${tag(el)}> \u4E0A \u2014\u2014 \u5305\u88F9\u540E\u4F1A\u53D8\u6210\u91CD\u590D id`
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
if (id === THEME_PT_ID && el !== theme) {
|
|
646
|
+
throw new TypeError(
|
|
647
|
+
`wrap(): \u4FDD\u7559 id ${THEME_PT_ID} \u53EA\u8BB8\u51FA\u73B0\u5728\u4E3B\u9898\u5757\u4E0A\uFF0C\u8FD9\u91CC\u51FA\u73B0\u5728 <${tag(el)}> \u4E0A`
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
function unwrap(doc) {
|
|
653
|
+
if (!doc || doc.nodeType !== 9) {
|
|
654
|
+
throw new TypeError("unwrap(): \u5165\u53C2\u5FC5\u987B\u662F Document");
|
|
655
|
+
}
|
|
656
|
+
const clone = doc.cloneNode(true);
|
|
657
|
+
const body = clone.body;
|
|
658
|
+
if (!body) throw new TypeError("unwrap(): \u6587\u6863\u6CA1\u6709 <body>");
|
|
659
|
+
const section = onlyWrapChild(body, "body");
|
|
660
|
+
const container = onlyWrapChild(section, `<${tag(section)} ${WRAP_ATTR}>`);
|
|
661
|
+
const div = clone.createElement("div");
|
|
662
|
+
const attrs = body.attributes;
|
|
663
|
+
for (let i = 0; i < attrs.length; i++) div.setAttribute(attrs[i].name, attrs[i].value);
|
|
664
|
+
while (container.firstChild) div.appendChild(container.firstChild);
|
|
665
|
+
const out = clone.createDocumentFragment();
|
|
666
|
+
const theme = clone.head?.querySelector('style[type="text/tailwindcss"]') ?? null;
|
|
667
|
+
if (theme) out.appendChild(theme);
|
|
668
|
+
const children = Array.from(body.childNodes);
|
|
669
|
+
for (const node of children) {
|
|
670
|
+
out.appendChild(node === section ? div : node);
|
|
671
|
+
}
|
|
672
|
+
return out;
|
|
673
|
+
}
|
|
674
|
+
function onlyWrapChild(parent, label) {
|
|
675
|
+
const kids = parent.children;
|
|
676
|
+
if (kids.length !== 1) {
|
|
677
|
+
throw new TypeError(
|
|
678
|
+
`unwrap(): ${label} \u4E0B\u5FC5\u987B\u6070\u6709\u4E00\u4E2A\u5408\u6210\u5C42\u5B50\u5143\u7D20\uFF0C\u5B9E\u9645\u6709 ${kids.length} \u4E2A \u2014\u2014 \u4E0D\u731C\u4E00\u4E2A\u51FA\u6765\u3002\u8FD9\u4EFD\u6587\u6863\u4E0D\u662F wrap() \u7684\u4EA7\u7269\uFF0C\u6216\u8005\u88AB\u6539\u6210\u4E86\u522B\u7684\u7ED3\u6784\u3002`
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
const only = kids[0];
|
|
682
|
+
if (!only.hasAttribute(WRAP_ATTR)) {
|
|
683
|
+
throw new TypeError(
|
|
684
|
+
`unwrap(): ${label} \u4E0B\u552F\u4E00\u7684\u5B50\u5143\u7D20\u6CA1\u6709 ${WRAP_ATTR} \u6807\u8BB0 \u2014\u2014 \u8FD9\u4EFD\u6587\u6863\u6CA1\u6709\u88AB wrap() \u5305\u88F9\u8FC7\u3002`
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
return only;
|
|
688
|
+
}
|
|
689
|
+
function describeNode(node) {
|
|
690
|
+
if (node.nodeType === 8) return "\u6CE8\u91CA\u8282\u70B9";
|
|
691
|
+
if (node.nodeType === 3) return `\u975E\u7A7A\u767D\u6587\u672C ${JSON.stringify(node.data.trim())}`;
|
|
692
|
+
return `nodeType ${node.nodeType}`;
|
|
565
693
|
}
|
|
566
694
|
|
|
567
695
|
// src/validate/variants.ts
|
|
@@ -629,16 +757,354 @@ function isNativePaletteClass(className) {
|
|
|
629
757
|
return SHADE.test(shade) && PALETTE.has(name);
|
|
630
758
|
}
|
|
631
759
|
|
|
760
|
+
// src/components/contracts.ts
|
|
761
|
+
var carouselContract = {
|
|
762
|
+
type: "carousel",
|
|
763
|
+
fields: [
|
|
764
|
+
// image 档不渲染标题/副标题/按钮 —— 是结构差异,不是外观档位。
|
|
765
|
+
{ id: "layout", type: "enum", values: ["hero", "image"], default: "hero" },
|
|
766
|
+
{ id: "autoplay", type: "boolean", default: true },
|
|
767
|
+
{ id: "interval", type: "number", default: 5e3 },
|
|
768
|
+
{ id: "showDots", type: "boolean", default: true },
|
|
769
|
+
{ id: "showArrows", type: "boolean", default: true }
|
|
770
|
+
],
|
|
771
|
+
blocks: [
|
|
772
|
+
{
|
|
773
|
+
type: "slide",
|
|
774
|
+
fields: [
|
|
775
|
+
{ id: "image", type: "image", default: "" },
|
|
776
|
+
{ id: "title", type: "text", default: "" },
|
|
777
|
+
{ id: "subtitle", type: "text", default: "" },
|
|
778
|
+
{ id: "buttonText", type: "text", default: "" },
|
|
779
|
+
{ id: "buttonUrl", type: "url", default: "#" }
|
|
780
|
+
]
|
|
781
|
+
}
|
|
782
|
+
]
|
|
783
|
+
};
|
|
784
|
+
var faqContract = {
|
|
785
|
+
type: "faq",
|
|
786
|
+
fields: [
|
|
787
|
+
{ id: "title", type: "text", default: "" },
|
|
788
|
+
// list 档不出 <details>、不出脚本 —— 两套结构。
|
|
789
|
+
{ id: "style", type: "enum", values: ["accordion", "list"], default: "accordion" },
|
|
790
|
+
{ id: "iconStyle", type: "enum", values: ["chevron", "plus"], default: "chevron" }
|
|
791
|
+
],
|
|
792
|
+
blocks: [
|
|
793
|
+
{
|
|
794
|
+
type: "item",
|
|
795
|
+
fields: [
|
|
796
|
+
{ id: "question", type: "text", default: "" },
|
|
797
|
+
{ id: "answer", type: "richtext", default: "" },
|
|
798
|
+
{ id: "defaultOpen", type: "boolean", default: false }
|
|
799
|
+
]
|
|
800
|
+
}
|
|
801
|
+
]
|
|
802
|
+
};
|
|
803
|
+
var tabsContract = {
|
|
804
|
+
type: "tabs",
|
|
805
|
+
fields: [
|
|
806
|
+
{ id: "title", type: "text", default: "" },
|
|
807
|
+
{ id: "tabStyle", type: "enum", values: ["underline", "pill", "boxed"], default: "underline" },
|
|
808
|
+
{ id: "defaultTab", type: "number", default: 0 }
|
|
809
|
+
],
|
|
810
|
+
blocks: [
|
|
811
|
+
{
|
|
812
|
+
type: "tab",
|
|
813
|
+
fields: [
|
|
814
|
+
{ id: "title", type: "text", default: "" },
|
|
815
|
+
{ id: "content", type: "richtext", default: "" },
|
|
816
|
+
{ id: "icon", type: "text", default: "" }
|
|
817
|
+
]
|
|
818
|
+
}
|
|
819
|
+
]
|
|
820
|
+
};
|
|
821
|
+
var formContract = {
|
|
822
|
+
type: "form",
|
|
823
|
+
fields: [
|
|
824
|
+
// 外部提交按钮按它定位表单 —— 判据②。
|
|
825
|
+
{ id: "formName", type: "text", default: "" },
|
|
826
|
+
{ id: "showSubmit", type: "boolean", default: true },
|
|
827
|
+
{ id: "submitText", type: "text", default: "" },
|
|
828
|
+
// 四档各一套 glyph markup;空串档 = 不出 glyph。
|
|
829
|
+
{ id: "submitIcon", type: "enum", values: ["", "email", "arrow", "check"], default: "" },
|
|
830
|
+
// richtext 而非 text:v2 原样插入它(`componentRenderers.ts:303` 无 esc),
|
|
831
|
+
// 而页脚正是隐私政策链接的所在。定成 text 会把 <a> 渲成字面字符。
|
|
832
|
+
// 紧邻的 successMessage 是转义的(同文件 :225)—— 这两个字段在 v2 里行为本就
|
|
833
|
+
// 不同,schema 却都标成 textarea,又一处只活在渲染器里的区别。
|
|
834
|
+
{ id: "footerText", type: "richtext", default: "" },
|
|
835
|
+
{ id: "successMessage", type: "text", default: "" },
|
|
836
|
+
{ id: "multiStep", type: "boolean", default: false },
|
|
837
|
+
// inline 是另一套 markup(label 在旁 vs 在上),不是对齐取值。
|
|
838
|
+
{ id: "labelPosition", type: "enum", values: ["top", "inline"], default: "top" },
|
|
839
|
+
{ id: "requiredStyle", type: "enum", values: ["asterisk", "badge"], default: "asterisk" },
|
|
840
|
+
{ id: "requiredBadgeText", type: "text", default: "" },
|
|
841
|
+
// 决定 <form action method> —— 判据②。
|
|
842
|
+
{ id: "formAction", type: "url", default: "" }
|
|
843
|
+
],
|
|
844
|
+
blocks: [
|
|
845
|
+
{
|
|
846
|
+
type: "field",
|
|
847
|
+
fields: [
|
|
848
|
+
{ id: "label", type: "text", default: "" },
|
|
849
|
+
// 提交数据的键名。v2 默认 'field',不照抄:多个字段同键会静默产出错数据。
|
|
850
|
+
// 空串**也不是**可见的错 —— HTML 的表单提交算法会跳过 name 为空的控件,
|
|
851
|
+
// 那一栏的数据就这么没了,页面上一点痕迹都没有。选空串是因为它是唯一
|
|
852
|
+
// **可辨识**的「作者没填」:'field' 与作者真的填了 'field' 无法区分。
|
|
853
|
+
// 由此 2.6 的渲染器负责把它变成可见的错:控件型 fieldType(label / step
|
|
854
|
+
// 之外)拿到空 name 即渲染失败抛错(D4)—— 画布上是占位、导出直接失败,
|
|
855
|
+
// 而不是发布之后才发现表单少收一栏。
|
|
856
|
+
{ id: "name", type: "text", default: "" },
|
|
857
|
+
{
|
|
858
|
+
id: "fieldType",
|
|
859
|
+
type: "enum",
|
|
860
|
+
values: [
|
|
861
|
+
"text",
|
|
862
|
+
"email",
|
|
863
|
+
"tel",
|
|
864
|
+
"number",
|
|
865
|
+
"textarea",
|
|
866
|
+
"select",
|
|
867
|
+
"checkbox",
|
|
868
|
+
"radio",
|
|
869
|
+
"date",
|
|
870
|
+
"label",
|
|
871
|
+
"step"
|
|
872
|
+
],
|
|
873
|
+
default: "text"
|
|
874
|
+
},
|
|
875
|
+
{ id: "placeholder", type: "text", default: "" },
|
|
876
|
+
// select / checkbox / radio 的选项表。v2 存成换行分隔的字符串,渲染器要用
|
|
877
|
+
// /\r?\n|\\n/ 去拆 —— 那个 \\n 分支的存在本身就说明 AI 写出过字面量反斜杠 n。
|
|
878
|
+
{ id: "options", type: "list", default: [] },
|
|
879
|
+
{ id: "required", type: "boolean", default: false },
|
|
880
|
+
{ id: "width", type: "enum", values: ["full", "half"], default: "full" }
|
|
881
|
+
]
|
|
882
|
+
}
|
|
883
|
+
]
|
|
884
|
+
};
|
|
885
|
+
var stickyCtaContract = {
|
|
886
|
+
type: "sticky-cta",
|
|
887
|
+
fields: [
|
|
888
|
+
{ id: "buttonText", type: "text", default: "" },
|
|
889
|
+
{ id: "buttonUrl", type: "url", default: "#" },
|
|
890
|
+
// 按钮左侧的说明文字。
|
|
891
|
+
{ id: "text", type: "text", default: "" },
|
|
892
|
+
// 滚动多少 px 之后出现 —— 判据③,脚本读。
|
|
893
|
+
{ id: "showAfter", type: "number", default: 0 },
|
|
894
|
+
{ id: "showOn", type: "enum", values: ["mobile", "all"], default: "mobile" },
|
|
895
|
+
// 条内排布模式(justify-content)而非纯对齐取值 —— 根元素的 text-* 顶不上去。
|
|
896
|
+
{ id: "align", type: "enum", values: ["center", "between"], default: "center" }
|
|
897
|
+
]
|
|
898
|
+
};
|
|
899
|
+
var COMPONENT_CONTRACTS = {
|
|
900
|
+
[carouselContract.type]: carouselContract,
|
|
901
|
+
[faqContract.type]: faqContract,
|
|
902
|
+
[tabsContract.type]: tabsContract,
|
|
903
|
+
[formContract.type]: formContract,
|
|
904
|
+
[stickyCtaContract.type]: stickyCtaContract
|
|
905
|
+
};
|
|
906
|
+
var COMPONENT_TYPES = Object.keys(COMPONENT_CONTRACTS);
|
|
907
|
+
|
|
908
|
+
// src/components/settings.ts
|
|
909
|
+
var BLOCKS_KEY = "_blocks";
|
|
910
|
+
|
|
911
|
+
// src/components/resolve.ts
|
|
912
|
+
function matches(spec, value) {
|
|
913
|
+
switch (spec.type) {
|
|
914
|
+
case "text":
|
|
915
|
+
case "richtext":
|
|
916
|
+
case "url":
|
|
917
|
+
case "image":
|
|
918
|
+
return typeof value === "string";
|
|
919
|
+
case "number":
|
|
920
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
921
|
+
case "boolean":
|
|
922
|
+
return typeof value === "boolean";
|
|
923
|
+
case "enum":
|
|
924
|
+
return typeof value === "string" && spec.values.includes(value);
|
|
925
|
+
case "list":
|
|
926
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
927
|
+
default: {
|
|
928
|
+
const never = spec;
|
|
929
|
+
throw new Error(`resolveComponent: \u672A\u5904\u7406\u7684\u53D6\u503C\u7C7B\u578B ${JSON.stringify(never)}`);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
function resolveFields(fields, raw) {
|
|
934
|
+
const out = {};
|
|
935
|
+
for (const spec of fields) {
|
|
936
|
+
const given = raw?.[spec.id];
|
|
937
|
+
out[spec.id] = given !== void 0 && matches(spec, given) ? given : spec.default;
|
|
938
|
+
}
|
|
939
|
+
return out;
|
|
940
|
+
}
|
|
941
|
+
function rawBlocks(raw) {
|
|
942
|
+
const value = raw[BLOCKS_KEY];
|
|
943
|
+
if (!Array.isArray(value)) return [];
|
|
944
|
+
return value.filter(
|
|
945
|
+
(b) => typeof b === "object" && b !== null
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
function blockSpecOf(blocks, type) {
|
|
949
|
+
if (typeof type !== "string") return void 0;
|
|
950
|
+
return blocks?.find((b) => b.type === type);
|
|
951
|
+
}
|
|
952
|
+
function resolveComponent(contract, raw = {}) {
|
|
953
|
+
const blocks = [];
|
|
954
|
+
for (const block of rawBlocks(raw)) {
|
|
955
|
+
const spec = blockSpecOf(contract.blocks, block.type);
|
|
956
|
+
if (!spec) continue;
|
|
957
|
+
const settings = typeof block.settings === "object" && block.settings !== null ? block.settings : void 0;
|
|
958
|
+
blocks.push({ type: spec.type, settings: resolveFields(spec.fields, settings) });
|
|
959
|
+
}
|
|
960
|
+
return {
|
|
961
|
+
type: contract.type,
|
|
962
|
+
settings: resolveFields(contract.fields, raw),
|
|
963
|
+
blocks
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// src/validate/components.ts
|
|
968
|
+
var SETTINGS_ATTR = "data-pt-component-settings";
|
|
969
|
+
var TYPE_ATTR = "data-pt-component";
|
|
970
|
+
function brief(value) {
|
|
971
|
+
if (typeof value === "string") return value.length > 40 ? `${value.slice(0, 40)}\u2026` : value;
|
|
972
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
973
|
+
if (value === null) return "null";
|
|
974
|
+
if (Array.isArray(value)) return "array";
|
|
975
|
+
return typeof value;
|
|
976
|
+
}
|
|
977
|
+
function checkFields(fields, settings, base, ptId, issues) {
|
|
978
|
+
const known = new Map(fields.map((spec) => [spec.id, spec]));
|
|
979
|
+
for (const key of Object.keys(settings)) {
|
|
980
|
+
if (key === BLOCKS_KEY) continue;
|
|
981
|
+
const spec = known.get(key);
|
|
982
|
+
if (!spec) {
|
|
983
|
+
issues.push({ ptId, rule: "COMPONENT_SETTING_UNKNOWN", params: { ...base, field: key } });
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
const value = settings[key];
|
|
987
|
+
if (value === void 0) continue;
|
|
988
|
+
if (matches(spec, value)) continue;
|
|
989
|
+
issues.push({
|
|
990
|
+
ptId,
|
|
991
|
+
rule: "COMPONENT_SETTING_INVALID",
|
|
992
|
+
params: {
|
|
993
|
+
...base,
|
|
994
|
+
field: key,
|
|
995
|
+
// 枚举越界与类型不符在这里分得开:前者类型对、只是取值不在集合里。
|
|
996
|
+
reason: spec.type === "enum" && typeof value === "string" ? "enum" : "type",
|
|
997
|
+
expected: spec.type,
|
|
998
|
+
value: brief(value)
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
function checkBlocks(specs, raw, component, ptId, issues) {
|
|
1004
|
+
if (raw === void 0) return;
|
|
1005
|
+
if (!Array.isArray(raw)) {
|
|
1006
|
+
issues.push({
|
|
1007
|
+
ptId,
|
|
1008
|
+
rule: "COMPONENT_SETTING_INVALID",
|
|
1009
|
+
params: { component, field: BLOCKS_KEY, reason: "type", expected: "array", value: brief(raw) }
|
|
1010
|
+
});
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
for (const [index, block] of raw.entries()) {
|
|
1014
|
+
if (typeof block !== "object" || block === null) {
|
|
1015
|
+
issues.push({
|
|
1016
|
+
ptId,
|
|
1017
|
+
rule: "COMPONENT_SETTING_INVALID",
|
|
1018
|
+
params: { component, field: BLOCKS_KEY, index, reason: "type", expected: "object", value: brief(block) }
|
|
1019
|
+
});
|
|
1020
|
+
continue;
|
|
1021
|
+
}
|
|
1022
|
+
const type = block.type;
|
|
1023
|
+
const spec = specs?.find((b) => b.type === type);
|
|
1024
|
+
if (!spec) {
|
|
1025
|
+
issues.push({
|
|
1026
|
+
ptId,
|
|
1027
|
+
rule: "COMPONENT_SETTING_UNKNOWN",
|
|
1028
|
+
params: { component, field: BLOCKS_KEY, index, block: brief(type) }
|
|
1029
|
+
});
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
const settings = block.settings;
|
|
1033
|
+
if (settings === void 0) continue;
|
|
1034
|
+
if (typeof settings !== "object" || settings === null) {
|
|
1035
|
+
issues.push({
|
|
1036
|
+
ptId,
|
|
1037
|
+
rule: "COMPONENT_SETTING_INVALID",
|
|
1038
|
+
params: { component, field: BLOCKS_KEY, index, reason: "type", expected: "object", value: brief(settings) }
|
|
1039
|
+
});
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
checkFields(
|
|
1043
|
+
spec.fields,
|
|
1044
|
+
settings,
|
|
1045
|
+
{ component, block: spec.type, index },
|
|
1046
|
+
ptId,
|
|
1047
|
+
issues
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
function checkComponentSettings(el, ptId, options, issues) {
|
|
1052
|
+
const component = el.getAttribute(TYPE_ATTR);
|
|
1053
|
+
if (component === null) return;
|
|
1054
|
+
const contract = COMPONENT_CONTRACTS[component];
|
|
1055
|
+
if (!contract) {
|
|
1056
|
+
if (options.knownComponentsOnly) {
|
|
1057
|
+
issues.push({ ptId, rule: "COMPONENT_SETTING_UNKNOWN", params: { component } });
|
|
1058
|
+
}
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
const rawText = el.getAttribute(SETTINGS_ATTR);
|
|
1062
|
+
if (rawText === null || rawText === "") return;
|
|
1063
|
+
let parsed;
|
|
1064
|
+
try {
|
|
1065
|
+
parsed = JSON.parse(rawText);
|
|
1066
|
+
} catch {
|
|
1067
|
+
issues.push({
|
|
1068
|
+
ptId,
|
|
1069
|
+
rule: "COMPONENT_SETTING_INVALID",
|
|
1070
|
+
params: { component, reason: "json" }
|
|
1071
|
+
});
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1075
|
+
issues.push({
|
|
1076
|
+
ptId,
|
|
1077
|
+
rule: "COMPONENT_SETTING_INVALID",
|
|
1078
|
+
params: { component, reason: "type", expected: "object", value: brief(parsed) }
|
|
1079
|
+
});
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
const settings = parsed;
|
|
1083
|
+
checkFields(contract.fields, settings, { component }, ptId, issues);
|
|
1084
|
+
checkBlocks(contract.blocks, settings[BLOCKS_KEY], component, ptId, issues);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
632
1087
|
// src/validate/index.ts
|
|
633
1088
|
var EVENT_ATTR = /^on[a-z]/i;
|
|
634
|
-
function validate(root, options) {
|
|
1089
|
+
function validate(root, options = {}) {
|
|
635
1090
|
const issues = [];
|
|
636
|
-
|
|
637
|
-
if (isDocument) checkFormatMeta(root, issues);
|
|
1091
|
+
if (isDoc(root)) checkFormatMeta(root, issues);
|
|
638
1092
|
checkIds(root, issues);
|
|
639
1093
|
checkElements(root, options, issues);
|
|
1094
|
+
if (options.singleSection) checkSingleSection(root, issues);
|
|
640
1095
|
return { ok: issues.length === 0, issues };
|
|
641
1096
|
}
|
|
1097
|
+
function checkSingleSection(root, issues) {
|
|
1098
|
+
const sections = root.querySelectorAll("section");
|
|
1099
|
+
if (sections.length <= 1) return;
|
|
1100
|
+
for (let i = 1; i < sections.length; i++) {
|
|
1101
|
+
issues.push({
|
|
1102
|
+
ptId: sections[i].getAttribute("data-pt-id") ?? void 0,
|
|
1103
|
+
rule: "STRUCTURE",
|
|
1104
|
+
params: { reason: "multiple-sections", count: sections.length }
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
642
1108
|
function isDoc(root) {
|
|
643
1109
|
return root.nodeType === 9;
|
|
644
1110
|
}
|
|
@@ -686,6 +1152,7 @@ function checkElements(root, options, issues) {
|
|
|
686
1152
|
}
|
|
687
1153
|
checkAttributes(el, ptId, issues);
|
|
688
1154
|
checkClasses(el, ptId, options, issues);
|
|
1155
|
+
checkComponentSettings(el, ptId, options, issues);
|
|
689
1156
|
checkPlaceholder(el, ptId, issues);
|
|
690
1157
|
}
|
|
691
1158
|
}
|
|
@@ -723,11 +1190,27 @@ function checkClasses(el, ptId, options, issues) {
|
|
|
723
1190
|
issues.push({ ptId, rule: "NATIVE_PALETTE", params: { className: cls } });
|
|
724
1191
|
continue;
|
|
725
1192
|
}
|
|
1193
|
+
if (options.allowVhUnits === false) {
|
|
1194
|
+
const unit = viewportUnitIn(cls);
|
|
1195
|
+
if (unit) {
|
|
1196
|
+
issues.push({ ptId, rule: "UNIT_FORBIDDEN", params: { className: cls, unit } });
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
726
1200
|
if (options.canCompileClass && !options.canCompileClass(cls)) {
|
|
727
1201
|
issues.push({ ptId, rule: "CLASS_UNCOMPILABLE", params: { className: cls } });
|
|
728
1202
|
}
|
|
729
1203
|
}
|
|
730
1204
|
}
|
|
1205
|
+
var ARBITRARY_VIEWPORT_UNIT = /\[[^\]]*?\d(?:\.\d+)?(l|s|d)?(vh|vw|vmin|vmax|vb|vi)\b/;
|
|
1206
|
+
var SCREEN_SCALE = /(^|:)(min-|max-)?[hw]-(screen|[lsd]vh|[lsd]vw|dvh|dvw|svh|svw|lvh|lvw)$/;
|
|
1207
|
+
function viewportUnitIn(cls) {
|
|
1208
|
+
const arbitrary = ARBITRARY_VIEWPORT_UNIT.exec(cls);
|
|
1209
|
+
if (arbitrary) return `${arbitrary[1] ?? ""}${arbitrary[2]}`;
|
|
1210
|
+
const scale = SCREEN_SCALE.exec(cls);
|
|
1211
|
+
if (scale) return scale[3];
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
731
1214
|
function checkPlaceholder(el, ptId, issues) {
|
|
732
1215
|
if (!el.hasAttribute("data-pt-placeholder")) return;
|
|
733
1216
|
const cls = el.getAttribute("class") ?? "";
|
|
@@ -762,6 +1245,12 @@ var ERROR_CODES = [
|
|
|
762
1245
|
"INLINE_STYLE",
|
|
763
1246
|
/** 使用了 Tailwind 内置色板。它本身可编译,所以必须由独立规则拦。 */
|
|
764
1247
|
"NATIVE_PALETTE",
|
|
1248
|
+
/**
|
|
1249
|
+
* 用了视口相对单位(vh / dvh / h-screen…),而当前调用方关掉了它
|
|
1250
|
+
* (`validate(doc, { allowVhUnits: false })`)。同 NATIVE_PALETTE:它可编译,
|
|
1251
|
+
* 拒绝它是产品决定,所以「可编译」判定拦不住,必须独立一条。
|
|
1252
|
+
*/
|
|
1253
|
+
"UNIT_FORBIDDEN",
|
|
765
1254
|
/** class 在当前 @theme 下编译不出 CSS。 */
|
|
766
1255
|
"CLASS_UNCOMPILABLE",
|
|
767
1256
|
/** 变体前缀不在允许集内,或组合顺序不是「设备在前、状态在后」。 */
|
|
@@ -777,7 +1266,27 @@ var ERROR_CODES = [
|
|
|
777
1266
|
/** 片段 canonical 往返不一致,说明 HTML 畸形。 */
|
|
778
1267
|
"FRAGMENT_NOT_CANONICAL",
|
|
779
1268
|
/** 存在未落图的图片占位 —— 阻塞发布,不阻塞预览。 */
|
|
780
|
-
"PLACEHOLDER_UNRESOLVED"
|
|
1269
|
+
"PLACEHOLDER_UNRESOLVED",
|
|
1270
|
+
/**
|
|
1271
|
+
* 渲染了一个注册表里没有的组件类型。
|
|
1272
|
+
*
|
|
1273
|
+
* 渲染**不返回降级标记**(v2 会返回一段「渲染失败」的灰色 div),所以这条必须抛。
|
|
1274
|
+
* 画布可以捕获后显示自己的占位(文案在宿主的三语字典里),成品拼装则应当整体失败 ——
|
|
1275
|
+
* 吞掉它就会静默产出一份缺了一块的成品,用户发布完才发现。
|
|
1276
|
+
*/
|
|
1277
|
+
"COMPONENT_UNKNOWN",
|
|
1278
|
+
/** 渲染器自身抛错。同上,不降级、不吞。 */
|
|
1279
|
+
"COMPONENT_RENDER_FAILED",
|
|
1280
|
+
/**
|
|
1281
|
+
* 组件设置里出现了契约中不存在的字段。
|
|
1282
|
+
*
|
|
1283
|
+
* 这是**危险的那个方向**:写了一个渲染器不认的字段 → 渲染器按默认值渲染 →
|
|
1284
|
+
* 改了没反应、无报错(这个仓对弹窗内置件烧过同样的账)。反方向(契约有、面板
|
|
1285
|
+
* 没暴露)只是少个入口,无声无害,不报。
|
|
1286
|
+
*/
|
|
1287
|
+
"COMPONENT_SETTING_UNKNOWN",
|
|
1288
|
+
/** 组件设置的取值类型不符、枚举越界,或设置 JSON 本身解析不出来。 */
|
|
1289
|
+
"COMPONENT_SETTING_INVALID"
|
|
781
1290
|
];
|
|
782
1291
|
var ERROR_CODE_SET = new Set(ERROR_CODES);
|
|
783
1292
|
function isErrorCode(value) {
|
|
@@ -795,13 +1304,13 @@ function editorError(code, extra) {
|
|
|
795
1304
|
|
|
796
1305
|
// src/validate/structure.ts
|
|
797
1306
|
var NOT_A_LAYER = /* @__PURE__ */ new Set(["html", "head", "body"]);
|
|
798
|
-
|
|
1307
|
+
var LEAF_TYPE_SET = new Set(LEAF_TYPES);
|
|
1308
|
+
function layerOf(el) {
|
|
799
1309
|
const name = tag(el);
|
|
800
1310
|
if (NOT_A_LAYER.has(name)) return null;
|
|
801
|
-
|
|
802
|
-
if (hasSectionLayer && name === "section") return "section";
|
|
1311
|
+
if (name === "section") return "section";
|
|
803
1312
|
const ptType = el.getAttribute("data-pt-type");
|
|
804
|
-
if (ptType &&
|
|
1313
|
+
if (ptType && LEAF_TYPE_SET.has(ptType)) return "leaf";
|
|
805
1314
|
if (el.hasAttribute("data-pt-component")) return "leaf";
|
|
806
1315
|
const opaque = closestOpaque(el);
|
|
807
1316
|
if (opaque && opaque.host === el) return "leaf";
|
|
@@ -809,19 +1318,19 @@ function layerOf(el, profile) {
|
|
|
809
1318
|
return null;
|
|
810
1319
|
}
|
|
811
1320
|
var MAX_CONTAINER_DEPTH = 2;
|
|
812
|
-
function containerDepth(el
|
|
1321
|
+
function containerDepth(el) {
|
|
813
1322
|
let depth = 0;
|
|
814
1323
|
let cur = el;
|
|
815
1324
|
while (cur) {
|
|
816
|
-
if (layerOf(cur
|
|
1325
|
+
if (layerOf(cur) === "container" && !isWrapLayer(cur)) depth += 1;
|
|
817
1326
|
cur = cur.parentElement;
|
|
818
1327
|
}
|
|
819
1328
|
return depth;
|
|
820
1329
|
}
|
|
821
|
-
function canContain(parent, childLayer
|
|
1330
|
+
function canContain(parent, childLayer) {
|
|
822
1331
|
const opaque = closestOpaque(parent);
|
|
823
1332
|
if (opaque) return "opaque-subtree";
|
|
824
|
-
const parentLayer = layerOf(parent
|
|
1333
|
+
const parentLayer = layerOf(parent);
|
|
825
1334
|
if (childLayer === "section") {
|
|
826
1335
|
const isBody = tag(parent) === "body";
|
|
827
1336
|
return isBody ? null : "section-not-body-child";
|
|
@@ -829,23 +1338,21 @@ function canContain(parent, childLayer, profile) {
|
|
|
829
1338
|
if (childLayer === "container") {
|
|
830
1339
|
if (parentLayer === "section") return null;
|
|
831
1340
|
if (parentLayer === "container") {
|
|
832
|
-
return containerDepth(parent
|
|
1341
|
+
return containerDepth(parent) + 1 > MAX_CONTAINER_DEPTH ? "container-too-deep" : null;
|
|
833
1342
|
}
|
|
834
|
-
if (!profile.layers.includes("section") && parentLayer === null) return null;
|
|
835
1343
|
return parentLayer === "leaf" ? "leaf-cannot-contain" : "unknown-layer";
|
|
836
1344
|
}
|
|
837
1345
|
if (parentLayer === "container") return null;
|
|
838
1346
|
if (parentLayer === "leaf") return "leaf-cannot-contain";
|
|
839
|
-
if (!profile.layers.includes("section") && parentLayer === null) return null;
|
|
840
1347
|
return "unknown-layer";
|
|
841
1348
|
}
|
|
842
|
-
function deleteTarget(el
|
|
843
|
-
const layer = layerOf(el
|
|
1349
|
+
function deleteTarget(el) {
|
|
1350
|
+
const layer = layerOf(el);
|
|
844
1351
|
if (layer !== "container") return el;
|
|
845
1352
|
const parent = el.parentElement;
|
|
846
|
-
if (!parent || layerOf(parent
|
|
1353
|
+
if (!parent || layerOf(parent) !== "section") return el;
|
|
847
1354
|
const siblingContainers = Array.from(parent.children).filter(
|
|
848
|
-
(c) => c !== el && layerOf(c
|
|
1355
|
+
(c) => c !== el && layerOf(c) === "container"
|
|
849
1356
|
);
|
|
850
1357
|
return siblingContainers.length === 0 ? parent : el;
|
|
851
1358
|
}
|
|
@@ -931,33 +1438,33 @@ function requireCanonicalFragment(doc, html) {
|
|
|
931
1438
|
|
|
932
1439
|
// src/ops/apply.ts
|
|
933
1440
|
var RAW_TEXT = /* @__PURE__ */ new Set(["style", "script", "xmp", "iframe", "noembed", "noframes", "plaintext"]);
|
|
934
|
-
function apply(doc, ops
|
|
1441
|
+
function apply(doc, ops) {
|
|
935
1442
|
if (!Array.isArray(ops)) throw new TypeError("apply(): ops \u5FC5\u987B\u662F\u6570\u7EC4");
|
|
936
1443
|
if (ops.length === 0) return { ok: true, inverse: [] };
|
|
937
1444
|
const rehearsal = doc.cloneNode(true);
|
|
938
|
-
const dry = run(rehearsal, ops
|
|
1445
|
+
const dry = run(rehearsal, ops);
|
|
939
1446
|
if (!dry.ok) return dry;
|
|
940
|
-
return run(doc, ops
|
|
1447
|
+
return run(doc, ops);
|
|
941
1448
|
}
|
|
942
|
-
function run(doc, ops
|
|
1449
|
+
function run(doc, ops) {
|
|
943
1450
|
const inverse = [];
|
|
944
1451
|
for (let i = 0; i < ops.length; i++) {
|
|
945
|
-
const result = applyOne(doc, ops[i]
|
|
1452
|
+
const result = applyOne(doc, ops[i]);
|
|
946
1453
|
if (!result.ok) return { ok: false, error: result.error, failedIndex: i };
|
|
947
1454
|
inverse.unshift(result.inverse);
|
|
948
1455
|
}
|
|
949
1456
|
return { ok: true, inverse };
|
|
950
1457
|
}
|
|
951
|
-
function applyOne(doc, op
|
|
1458
|
+
function applyOne(doc, op) {
|
|
952
1459
|
switch (op.op) {
|
|
953
1460
|
case "set":
|
|
954
1461
|
return applySet(doc, op);
|
|
955
1462
|
case "insert":
|
|
956
|
-
return applyInsert(doc, op
|
|
1463
|
+
return applyInsert(doc, op);
|
|
957
1464
|
case "delete":
|
|
958
|
-
return applyDelete(doc, op
|
|
1465
|
+
return applyDelete(doc, op);
|
|
959
1466
|
case "move":
|
|
960
|
-
return applyMove(doc, op
|
|
1467
|
+
return applyMove(doc, op);
|
|
961
1468
|
case "replace":
|
|
962
1469
|
return applyReplace(doc, op);
|
|
963
1470
|
default:
|
|
@@ -969,10 +1476,15 @@ function applyOne(doc, op, options) {
|
|
|
969
1476
|
};
|
|
970
1477
|
}
|
|
971
1478
|
}
|
|
1479
|
+
function rejectWrapTarget(el, ptId) {
|
|
1480
|
+
return isWrapLayer(el) ? editorError("STRUCTURE", { ptId, params: { reason: "wrap-layer-immutable" } }) : null;
|
|
1481
|
+
}
|
|
972
1482
|
function applySet(doc, op) {
|
|
973
1483
|
const miss = requireIdHit(doc, op.id);
|
|
974
1484
|
if (miss) return { ok: false, error: miss };
|
|
975
1485
|
const el = findById(doc, op.id);
|
|
1486
|
+
const wrapped = rejectWrapTarget(el, op.id);
|
|
1487
|
+
if (wrapped) return { ok: false, error: wrapped };
|
|
976
1488
|
const opaque = closestOpaque(el);
|
|
977
1489
|
if (opaque && opaque.host !== el && opaque.mode !== "text") {
|
|
978
1490
|
return { ok: false, error: editorError("STRUCTURE", { ptId: op.id, params: { reason: "opaque-subtree" } }) };
|
|
@@ -1018,10 +1530,10 @@ function applySet(doc, op) {
|
|
|
1018
1530
|
return { ok: true, inverse: inv };
|
|
1019
1531
|
}
|
|
1020
1532
|
function applyClasses(el, spec) {
|
|
1021
|
-
const
|
|
1022
|
-
const set = new Set(
|
|
1533
|
+
const list2 = normalizeClassValue(el.getAttribute("class") ?? "").split(" ").filter(Boolean);
|
|
1534
|
+
const set = new Set(list2);
|
|
1023
1535
|
for (const c of spec.remove ?? []) set.delete(c);
|
|
1024
|
-
const out =
|
|
1536
|
+
const out = list2.filter((c) => set.has(c));
|
|
1025
1537
|
for (const c of spec.add ?? []) {
|
|
1026
1538
|
if (!set.has(c)) {
|
|
1027
1539
|
set.add(c);
|
|
@@ -1065,7 +1577,7 @@ function setChildren(doc, el, text) {
|
|
|
1065
1577
|
}
|
|
1066
1578
|
el.appendChild(parseFragment(doc, text));
|
|
1067
1579
|
}
|
|
1068
|
-
function applyInsert(doc, op
|
|
1580
|
+
function applyInsert(doc, op) {
|
|
1069
1581
|
const miss = requireIdHit(doc, op.parentId);
|
|
1070
1582
|
if (miss) return { ok: false, error: miss };
|
|
1071
1583
|
const parent = findById(doc, op.parentId);
|
|
@@ -1079,11 +1591,11 @@ function applyInsert(doc, op, options) {
|
|
|
1079
1591
|
const idErr = requireFragmentIds(frag, collectDocIds(doc));
|
|
1080
1592
|
if (idErr) return { ok: false, error: idErr };
|
|
1081
1593
|
const root = fragmentElementRoots(frag)[0];
|
|
1082
|
-
const childLayer = layerOf(root
|
|
1594
|
+
const childLayer = layerOf(root);
|
|
1083
1595
|
if (!childLayer) {
|
|
1084
1596
|
return { ok: false, error: editorError("STRUCTURE", { params: { reason: "unknown-layer" } }) };
|
|
1085
1597
|
}
|
|
1086
|
-
const rejection = canContain(parent, childLayer
|
|
1598
|
+
const rejection = canContain(parent, childLayer);
|
|
1087
1599
|
if (rejection) {
|
|
1088
1600
|
return { ok: false, error: editorError("STRUCTURE", { ptId: op.parentId, params: { reason: rejection } }) };
|
|
1089
1601
|
}
|
|
@@ -1092,15 +1604,17 @@ function applyInsert(doc, op, options) {
|
|
|
1092
1604
|
parent.insertBefore(frag, parent.childNodes[at] ?? null);
|
|
1093
1605
|
return { ok: true, inverse: { op: "delete", id: newId } };
|
|
1094
1606
|
}
|
|
1095
|
-
function applyDelete(doc, op
|
|
1607
|
+
function applyDelete(doc, op) {
|
|
1096
1608
|
const miss = requireIdHit(doc, op.id);
|
|
1097
1609
|
if (miss) return { ok: false, error: miss };
|
|
1098
1610
|
const el = findById(doc, op.id);
|
|
1611
|
+
const wrapped = rejectWrapTarget(el, op.id);
|
|
1612
|
+
if (wrapped) return { ok: false, error: wrapped };
|
|
1099
1613
|
const opaque = closestOpaque(el);
|
|
1100
1614
|
if (opaque && opaque.host !== el) {
|
|
1101
1615
|
return { ok: false, error: editorError("STRUCTURE", { ptId: op.id, params: { reason: "opaque-subtree" } }) };
|
|
1102
1616
|
}
|
|
1103
|
-
const target = deleteTarget(el
|
|
1617
|
+
const target = deleteTarget(el);
|
|
1104
1618
|
const parent = target.parentElement;
|
|
1105
1619
|
if (!parent) {
|
|
1106
1620
|
return { ok: false, error: editorError("STRUCTURE", { ptId: op.id, params: { reason: "no-parent" } }) };
|
|
@@ -1118,21 +1632,23 @@ function applyDelete(doc, op, options) {
|
|
|
1118
1632
|
inverse: { op: "insert", parentId, index: 0, html, nodeIndex }
|
|
1119
1633
|
};
|
|
1120
1634
|
}
|
|
1121
|
-
function applyMove(doc, op
|
|
1635
|
+
function applyMove(doc, op) {
|
|
1122
1636
|
const missSelf = requireIdHit(doc, op.id);
|
|
1123
1637
|
if (missSelf) return { ok: false, error: missSelf };
|
|
1124
1638
|
const missParent = requireIdHit(doc, op.parentId);
|
|
1125
1639
|
if (missParent) return { ok: false, error: missParent };
|
|
1126
1640
|
const el = findById(doc, op.id);
|
|
1127
1641
|
const parent = findById(doc, op.parentId);
|
|
1642
|
+
const wrapped = rejectWrapTarget(el, op.id);
|
|
1643
|
+
if (wrapped) return { ok: false, error: wrapped };
|
|
1128
1644
|
if (el === parent || el.contains(parent)) {
|
|
1129
1645
|
return { ok: false, error: editorError("STRUCTURE", { ptId: op.id, params: { reason: "move-into-self" } }) };
|
|
1130
1646
|
}
|
|
1131
|
-
const layer = layerOf(el
|
|
1647
|
+
const layer = layerOf(el);
|
|
1132
1648
|
if (!layer) {
|
|
1133
1649
|
return { ok: false, error: editorError("STRUCTURE", { ptId: op.id, params: { reason: "unknown-layer" } }) };
|
|
1134
1650
|
}
|
|
1135
|
-
const rejection = canContain(parent, layer
|
|
1651
|
+
const rejection = canContain(parent, layer);
|
|
1136
1652
|
if (rejection) {
|
|
1137
1653
|
return { ok: false, error: editorError("STRUCTURE", { ptId: op.parentId, params: { reason: rejection } }) };
|
|
1138
1654
|
}
|
|
@@ -1153,6 +1669,8 @@ function applyReplace(doc, op) {
|
|
|
1153
1669
|
const miss = requireIdHit(doc, op.id);
|
|
1154
1670
|
if (miss) return { ok: false, error: miss };
|
|
1155
1671
|
const el = findById(doc, op.id);
|
|
1672
|
+
const wrapped = rejectWrapTarget(el, op.id);
|
|
1673
|
+
if (wrapped) return { ok: false, error: wrapped };
|
|
1156
1674
|
const canonical = requireCanonicalFragment(doc, op.html);
|
|
1157
1675
|
if (canonical) return { ok: false, error: canonical };
|
|
1158
1676
|
const frag = parseFragment(doc, op.html);
|
|
@@ -1382,6 +1900,450 @@ function clearCompileCache() {
|
|
|
1382
1900
|
cache.clear();
|
|
1383
1901
|
}
|
|
1384
1902
|
|
|
1903
|
+
// src/components/index.ts
|
|
1904
|
+
var components_exports = {};
|
|
1905
|
+
__export(components_exports, {
|
|
1906
|
+
BLOCKS_KEY: () => BLOCKS_KEY,
|
|
1907
|
+
COMPONENT_CONTRACTS: () => COMPONENT_CONTRACTS,
|
|
1908
|
+
COMPONENT_TYPES: () => COMPONENT_TYPES,
|
|
1909
|
+
SETTING_TYPES: () => SETTING_TYPES,
|
|
1910
|
+
carouselContract: () => carouselContract,
|
|
1911
|
+
faqContract: () => faqContract,
|
|
1912
|
+
formContract: () => formContract,
|
|
1913
|
+
renderCss: () => renderCss,
|
|
1914
|
+
renderHtml: () => renderHtml,
|
|
1915
|
+
renderJs: () => renderJs,
|
|
1916
|
+
resolveComponent: () => resolveComponent,
|
|
1917
|
+
stickyCtaContract: () => stickyCtaContract,
|
|
1918
|
+
tabsContract: () => tabsContract
|
|
1919
|
+
});
|
|
1920
|
+
|
|
1921
|
+
// src/components/renderers/shared.ts
|
|
1922
|
+
var esc = escapeText;
|
|
1923
|
+
var escAttr = escapeAttribute;
|
|
1924
|
+
function str(settings, id) {
|
|
1925
|
+
return settings[id];
|
|
1926
|
+
}
|
|
1927
|
+
function num(settings, id) {
|
|
1928
|
+
return settings[id];
|
|
1929
|
+
}
|
|
1930
|
+
function bool(settings, id) {
|
|
1931
|
+
return settings[id];
|
|
1932
|
+
}
|
|
1933
|
+
function list(settings, id) {
|
|
1934
|
+
return settings[id];
|
|
1935
|
+
}
|
|
1936
|
+
function componentScript(type, initBody) {
|
|
1937
|
+
if (!/^[a-z][a-z0-9-]*$/.test(type)) throw new TypeError(type);
|
|
1938
|
+
return `(function(){var T='[data-pt-component="${type}"]',F='__pt_${type.replace(/-/g, "_")}';function init(root){if(root[F])return;root[F]=1;${initBody}}function scan(){var l=document.querySelectorAll(T);for(var i=0;i<l.length;i++)init(l[i]);}if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',scan);else scan();})();`;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
// src/components/renderers/carousel.ts
|
|
1942
|
+
var carouselRenderer = {
|
|
1943
|
+
html(instance) {
|
|
1944
|
+
const layout = str(instance.settings, "layout");
|
|
1945
|
+
const imageOnly = layout === "image";
|
|
1946
|
+
const slides = instance.blocks.filter((b) => b.type === "slide");
|
|
1947
|
+
const many = slides.length > 1;
|
|
1948
|
+
const showDots = bool(instance.settings, "showDots") && many;
|
|
1949
|
+
const showArrows = bool(instance.settings, "showArrows") && many;
|
|
1950
|
+
const track = slides.map((slide) => {
|
|
1951
|
+
const image = str(slide.settings, "image");
|
|
1952
|
+
const title = str(slide.settings, "title");
|
|
1953
|
+
const subtitle = str(slide.settings, "subtitle");
|
|
1954
|
+
const buttonText = str(slide.settings, "buttonText");
|
|
1955
|
+
const img = image ? `<img class="ptc-carousel-img" src="${escAttr(image)}" alt="${escAttr(title)}" loading="lazy">` : "";
|
|
1956
|
+
const body = imageOnly ? "" : [
|
|
1957
|
+
title ? `<h3 class="ptc-carousel-title">${esc(title)}</h3>` : "",
|
|
1958
|
+
subtitle ? `<p class="ptc-carousel-sub">${esc(subtitle)}</p>` : "",
|
|
1959
|
+
buttonText ? `<a class="ptc-carousel-btn" href="${escAttr(str(slide.settings, "buttonUrl"))}">${esc(buttonText)}</a>` : ""
|
|
1960
|
+
].join("");
|
|
1961
|
+
return '<div class="ptc-carousel-slide">' + img + (body ? `<div class="ptc-carousel-body">${body}</div>` : "") + "</div>";
|
|
1962
|
+
}).join("");
|
|
1963
|
+
const dots = showDots ? '<div class="ptc-carousel-dots">' + slides.map(
|
|
1964
|
+
(_, i) => `<button type="button" class="ptc-carousel-dot" data-go="${i}" aria-current="${i === 0 ? "true" : "false"}"></button>`
|
|
1965
|
+
).join("") + "</div>" : "";
|
|
1966
|
+
const arrows = showArrows ? '<button type="button" class="ptc-carousel-arrow" data-go="prev">\u2039</button><button type="button" class="ptc-carousel-arrow" data-go="next">\u203A</button>' : "";
|
|
1967
|
+
return `<div class="ptc-carousel" data-carousel-layout="${escAttr(layout)}" data-autoplay="${bool(instance.settings, "autoplay") && many ? "true" : "false"}" data-interval="${num(instance.settings, "interval")}"><div class="ptc-carousel-track">${track}</div>` + dots + arrows + "</div>";
|
|
1968
|
+
},
|
|
1969
|
+
css() {
|
|
1970
|
+
return [
|
|
1971
|
+
".ptc-carousel{position:relative;width:100%}",
|
|
1972
|
+
// 滑动本体:不跑脚本也能用手指滑。
|
|
1973
|
+
".ptc-carousel-track{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;scroll-behavior:smooth;scrollbar-width:none}",
|
|
1974
|
+
".ptc-carousel-track::-webkit-scrollbar{display:none}",
|
|
1975
|
+
".ptc-carousel-slide{position:relative;flex:0 0 100%;scroll-snap-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:calc(var(--spacing)*2);aspect-ratio:16/9;overflow:hidden;background:var(--pt-carousel-slide-bg,color-mix(in oklab,currentColor 6%,transparent))}",
|
|
1976
|
+
".ptc-carousel-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}",
|
|
1977
|
+
".ptc-carousel-body{position:relative;padding:calc(var(--spacing)*6)}",
|
|
1978
|
+
".ptc-carousel-title{margin:0;font-size:2em;font-weight:700}",
|
|
1979
|
+
".ptc-carousel-sub{margin:calc(var(--spacing)*2) 0 0}",
|
|
1980
|
+
".ptc-carousel-btn{display:inline-block;margin-top:calc(var(--spacing)*4);padding:calc(var(--spacing)*3) calc(var(--spacing)*7);border-radius:var(--radius-lg);text-decoration:none;background:var(--pt-carousel-btn-bg,currentColor);color:var(--pt-carousel-btn-fg,white)}",
|
|
1981
|
+
".ptc-carousel-dots{position:absolute;left:0;right:0;bottom:calc(var(--spacing)*4);display:flex;justify-content:center;gap:calc(var(--spacing)*2)}",
|
|
1982
|
+
".ptc-carousel-dot{width:calc(var(--spacing)*2.5);height:calc(var(--spacing)*2.5);padding:0;border:0;border-radius:var(--radius-4xl);cursor:pointer;background:var(--pt-carousel-dot-off,color-mix(in oklab,currentColor 25%,transparent))}",
|
|
1983
|
+
'.ptc-carousel-dot[aria-current="true"]{background:var(--pt-carousel-dot-on,currentColor)}',
|
|
1984
|
+
".ptc-carousel-arrow{position:absolute;top:50%;translate:0 -50%;display:flex;align-items:center;justify-content:center;width:calc(var(--spacing)*9);height:calc(var(--spacing)*9);border:0;border-radius:var(--radius-4xl);cursor:pointer;font-size:1.25em;line-height:1;background:var(--pt-carousel-arrow-bg,color-mix(in oklab,currentColor 20%,transparent))}",
|
|
1985
|
+
'.ptc-carousel-arrow[data-go="prev"]{left:calc(var(--spacing)*3)}',
|
|
1986
|
+
'.ptc-carousel-arrow[data-go="next"]{right:calc(var(--spacing)*3)}',
|
|
1987
|
+
// image 档没有文案层,body 不渲染;这条只是把 padding 的余味也去掉。
|
|
1988
|
+
'[data-carousel-layout="image"] .ptc-carousel-slide{gap:0}',
|
|
1989
|
+
"@media (prefers-reduced-motion:reduce){.ptc-carousel-track{scroll-behavior:auto}}"
|
|
1990
|
+
].join("");
|
|
1991
|
+
},
|
|
1992
|
+
js() {
|
|
1993
|
+
return componentScript(
|
|
1994
|
+
"carousel",
|
|
1995
|
+
`var tk=root.querySelector('.ptc-carousel-track');if(!tk)return;var dt=root.querySelectorAll('.ptc-carousel-dot');function at(){return tk.clientWidth?Math.round(tk.scrollLeft/tk.clientWidth):0;}function go(i){var n=tk.children.length;if(!n)return;var t=((i%n)+n)%n;tk.scrollTo({left:t*tk.clientWidth,behavior:"smooth"});}function sync(){var c=at();for(var i=0;i<dt.length;i++)dt[i].setAttribute('aria-current',i===c?'true':'false');}tk.addEventListener('scroll',sync);root.addEventListener('click',function(e){var t=e.target,el=t&&t.closest?t.closest("[data-go]"):null;if(!el||!root.contains(el))return;var v=el.getAttribute('data-go');if(v==='prev')go(at()-1);else if(v==='next')go(at()+1);else go(parseInt(v,10));});if(root.getAttribute('data-autoplay')==='true'){var ms=parseInt(root.getAttribute('data-interval'),10);if(ms>0)setInterval(function(){go(at()+1);},ms);}sync();`
|
|
1996
|
+
);
|
|
1997
|
+
}
|
|
1998
|
+
};
|
|
1999
|
+
|
|
2000
|
+
// src/components/renderers/faq.ts
|
|
2001
|
+
function icon(kind) {
|
|
2002
|
+
if (kind === "plus") {
|
|
2003
|
+
return '<span class="ptc-faq-ic" aria-hidden="true"><span class="ptc-faq-ic-p">+</span><span class="ptc-faq-ic-m">\u2212</span></span>';
|
|
2004
|
+
}
|
|
2005
|
+
return '<span class="ptc-faq-ic" aria-hidden="true">\u203A</span>';
|
|
2006
|
+
}
|
|
2007
|
+
function item(question, answer, open, listStyle, ic) {
|
|
2008
|
+
const q = esc(question);
|
|
2009
|
+
if (listStyle) {
|
|
2010
|
+
return `<div class="ptc-faq-item"><div class="ptc-faq-q">${q}</div><div class="ptc-faq-a">${answer}</div></div>`;
|
|
2011
|
+
}
|
|
2012
|
+
return `<details class="ptc-faq-item"${open ? " open" : ""}><summary class="ptc-faq-q"><span>${q}</span>${ic}</summary><div class="ptc-faq-a">${answer}</div></details>`;
|
|
2013
|
+
}
|
|
2014
|
+
var faqRenderer = {
|
|
2015
|
+
html(instance) {
|
|
2016
|
+
const style = str(instance.settings, "style");
|
|
2017
|
+
const iconStyle = str(instance.settings, "iconStyle");
|
|
2018
|
+
const title = str(instance.settings, "title");
|
|
2019
|
+
const listStyle = style === "list";
|
|
2020
|
+
const ic = icon(iconStyle);
|
|
2021
|
+
const head = title ? `<h3 class="ptc-faq-title">${esc(title)}</h3>` : "";
|
|
2022
|
+
const items = instance.blocks.filter((b) => b.type === "item").map(
|
|
2023
|
+
(b) => item(
|
|
2024
|
+
str(b.settings, "question"),
|
|
2025
|
+
// richtext:按契约原样插入,不转义。
|
|
2026
|
+
str(b.settings, "answer"),
|
|
2027
|
+
b.settings["defaultOpen"] === true,
|
|
2028
|
+
listStyle,
|
|
2029
|
+
ic
|
|
2030
|
+
)
|
|
2031
|
+
).join("");
|
|
2032
|
+
return `<div class="ptc-faq" data-faq-style="${escAttr(style)}" data-faq-icon="${escAttr(iconStyle)}">` + head + `<div class="ptc-faq-items">${items}</div></div>`;
|
|
2033
|
+
},
|
|
2034
|
+
css() {
|
|
2035
|
+
return [
|
|
2036
|
+
".ptc-faq{width:100%}",
|
|
2037
|
+
".ptc-faq-title{margin:0 0 calc(var(--spacing)*10);font-size:1.5em;font-weight:700}",
|
|
2038
|
+
".ptc-faq-items{display:flex;flex-direction:column;gap:calc(var(--spacing)*2)}",
|
|
2039
|
+
".ptc-faq-item{border:1px solid var(--pt-faq-border,color-mix(in oklab,currentColor 15%,transparent));border-radius:var(--radius-lg);overflow:hidden}",
|
|
2040
|
+
".ptc-faq-q{display:flex;align-items:center;justify-content:space-between;gap:calc(var(--spacing)*3);padding:calc(var(--spacing)*3) calc(var(--spacing)*4);font-weight:500}",
|
|
2041
|
+
".ptc-faq-a{padding:calc(var(--spacing)*3) calc(var(--spacing)*4);line-height:1.6;color:var(--pt-faq-answer-fg,color-mix(in oklab,currentColor 70%,transparent))}",
|
|
2042
|
+
"details.ptc-faq-item>.ptc-faq-q{cursor:pointer;list-style:none;background:var(--pt-faq-summary-bg,color-mix(in oklab,currentColor 4%,transparent))}",
|
|
2043
|
+
"details.ptc-faq-item>.ptc-faq-q::-webkit-details-marker{display:none}",
|
|
2044
|
+
".ptc-faq-ic{flex:none;font-size:1.2em;line-height:1}",
|
|
2045
|
+
// chevron:靠旋转表达开合,一个字形。
|
|
2046
|
+
'[data-faq-icon="chevron"] .ptc-faq-ic{transition:transform .2s}',
|
|
2047
|
+
'[data-faq-icon="chevron"] details[open]>.ptc-faq-q .ptc-faq-ic{transform:rotate(90deg)}',
|
|
2048
|
+
// plus:两个字形,显隐由 [open] 决定,同样不需要脚本。
|
|
2049
|
+
'[data-faq-icon="plus"] .ptc-faq-ic-m{display:none}',
|
|
2050
|
+
'[data-faq-icon="plus"] details[open]>.ptc-faq-q .ptc-faq-ic-p{display:none}',
|
|
2051
|
+
'[data-faq-icon="plus"] details[open]>.ptc-faq-q .ptc-faq-ic-m{display:inline}',
|
|
2052
|
+
// list 档:没有 <details>,去掉交互态的余味。
|
|
2053
|
+
'[data-faq-style="list"] .ptc-faq-item{border:0;border-radius:0}',
|
|
2054
|
+
'[data-faq-style="list"] .ptc-faq-q{padding-inline:0;background:none}',
|
|
2055
|
+
'[data-faq-style="list"] .ptc-faq-a{padding-inline:0}'
|
|
2056
|
+
].join("");
|
|
2057
|
+
},
|
|
2058
|
+
js() {
|
|
2059
|
+
return componentScript(
|
|
2060
|
+
"faq",
|
|
2061
|
+
`root.addEventListener('click',function(e){var t=e.target,s=t&&t.closest?t.closest("summary"):null;if(!s||!root.contains(s))return;var d=s.parentNode;if(!d||d.tagName!=='DETAILS'||d.open)return;var l=root.querySelectorAll('details');for(var i=0;i<l.length;i++)if(l[i]!==d)l[i].removeAttribute('open');});`
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
};
|
|
2065
|
+
|
|
2066
|
+
// src/components/renderers/form.ts
|
|
2067
|
+
var NOT_A_CONTROL = /* @__PURE__ */ new Set(["label", "step"]);
|
|
2068
|
+
var INPUT_TYPES = /* @__PURE__ */ new Set(["email", "tel", "number", "date"]);
|
|
2069
|
+
var SUBMIT_ICONS = {
|
|
2070
|
+
email: "\u2709\uFE0E ",
|
|
2071
|
+
arrow: "\u2192 ",
|
|
2072
|
+
check: "\u2713 "
|
|
2073
|
+
};
|
|
2074
|
+
function requiredMark(style, badgeText) {
|
|
2075
|
+
if (style === "badge") return `<span class="ptc-form-badge">${esc(badgeText)}</span>`;
|
|
2076
|
+
return '<span class="ptc-form-star" aria-hidden="true">*</span>';
|
|
2077
|
+
}
|
|
2078
|
+
function control(field2, id, name) {
|
|
2079
|
+
const type = str(field2.settings, "fieldType");
|
|
2080
|
+
const required = bool(field2.settings, "required") ? " required" : "";
|
|
2081
|
+
const placeholder = str(field2.settings, "placeholder");
|
|
2082
|
+
const ph = placeholder ? ` placeholder="${escAttr(placeholder)}"` : "";
|
|
2083
|
+
if (type === "textarea") {
|
|
2084
|
+
return `<textarea class="ptc-form-input" id="${id}" name="${name}"${ph}${required}></textarea>`;
|
|
2085
|
+
}
|
|
2086
|
+
if (type === "select") {
|
|
2087
|
+
const options = list(field2.settings, "options").map((o) => `<option>${esc(o)}</option>`).join("");
|
|
2088
|
+
return `<select class="ptc-form-input" id="${id}" name="${name}"${required}>${options}</select>`;
|
|
2089
|
+
}
|
|
2090
|
+
if (type === "checkbox" || type === "radio") {
|
|
2091
|
+
return list(field2.settings, "options").map(
|
|
2092
|
+
(o, i) => `<label class="ptc-form-choice"><input type="${type}" name="${name}" value="${escAttr(o)}"${i === 0 ? required : ""}><span>${esc(o)}</span></label>`
|
|
2093
|
+
).join("");
|
|
2094
|
+
}
|
|
2095
|
+
const inputType = INPUT_TYPES.has(type) ? type : "text";
|
|
2096
|
+
return `<input class="ptc-form-input" type="${inputType}" id="${id}" name="${name}"${ph}${required}>`;
|
|
2097
|
+
}
|
|
2098
|
+
function field(block, index, instanceId, style, badge) {
|
|
2099
|
+
const type = str(block.settings, "fieldType");
|
|
2100
|
+
const label = str(block.settings, "label");
|
|
2101
|
+
if (NOT_A_CONTROL.has(type)) {
|
|
2102
|
+
return type === "label" ? `<div class="ptc-form-note">${esc(label)}</div>` : "";
|
|
2103
|
+
}
|
|
2104
|
+
const name = str(block.settings, "name");
|
|
2105
|
+
if (!name) {
|
|
2106
|
+
throw editorError("COMPONENT_RENDER_FAILED", {
|
|
2107
|
+
params: { type: "form", field: index, fieldType: type }
|
|
2108
|
+
});
|
|
2109
|
+
}
|
|
2110
|
+
const id = `${escAttr(instanceId)}-f${index}`;
|
|
2111
|
+
const mark = bool(block.settings, "required") ? requiredMark(style, badge) : "";
|
|
2112
|
+
const body = control(block, id, escAttr(name));
|
|
2113
|
+
const width = str(block.settings, "width");
|
|
2114
|
+
if (type === "checkbox" || type === "radio") {
|
|
2115
|
+
return `<fieldset class="ptc-form-field" data-width="${escAttr(width)}"><legend class="ptc-form-label">${esc(label)}${mark}</legend><div class="ptc-form-choices">${body}</div></fieldset>`;
|
|
2116
|
+
}
|
|
2117
|
+
return `<div class="ptc-form-field" data-width="${escAttr(width)}"><label class="ptc-form-label" for="${id}">${esc(label)}${mark}</label>${body}</div>`;
|
|
2118
|
+
}
|
|
2119
|
+
function intoSteps(blocks) {
|
|
2120
|
+
const steps = [];
|
|
2121
|
+
let current = { title: "", fields: [] };
|
|
2122
|
+
let started = false;
|
|
2123
|
+
for (const block of blocks) {
|
|
2124
|
+
if (str(block.settings, "fieldType") === "step") {
|
|
2125
|
+
if (started || current.fields.length > 0) steps.push(current);
|
|
2126
|
+
current = { title: str(block.settings, "label"), fields: [] };
|
|
2127
|
+
started = true;
|
|
2128
|
+
} else {
|
|
2129
|
+
current.fields.push(block);
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
steps.push(current);
|
|
2133
|
+
return steps;
|
|
2134
|
+
}
|
|
2135
|
+
var formRenderer = {
|
|
2136
|
+
html(instance, instanceId) {
|
|
2137
|
+
const settings = instance.settings;
|
|
2138
|
+
const blocks = instance.blocks.filter((b) => b.type === "field");
|
|
2139
|
+
const requiredStyle = str(settings, "requiredStyle");
|
|
2140
|
+
const badge = str(settings, "requiredBadgeText");
|
|
2141
|
+
const id = escAttr(instanceId);
|
|
2142
|
+
let seq = 0;
|
|
2143
|
+
const render = (block) => field(block, seq++, instanceId, requiredStyle, badge);
|
|
2144
|
+
const steps = bool(settings, "multiStep") ? intoSteps(blocks) : [];
|
|
2145
|
+
const stepped = steps.length > 1;
|
|
2146
|
+
let nav = "";
|
|
2147
|
+
let body;
|
|
2148
|
+
if (stepped) {
|
|
2149
|
+
nav = '<div class="ptc-form-steps">' + steps.map(
|
|
2150
|
+
(step, i) => `<button type="button" class="ptc-form-step-tab" data-step-to="${i}" aria-selected="${i === 0 ? "true" : "false"}">${esc(step.title)}</button>`
|
|
2151
|
+
).join("") + "</div>";
|
|
2152
|
+
body = steps.map(
|
|
2153
|
+
(step, i) => `<div class="ptc-form-step" data-step="${i}"${i === 0 ? "" : " hidden"}>` + step.fields.map(render).join("") + "</div>"
|
|
2154
|
+
).join("");
|
|
2155
|
+
} else {
|
|
2156
|
+
body = blocks.map(render).join("");
|
|
2157
|
+
}
|
|
2158
|
+
const action = str(settings, "formAction");
|
|
2159
|
+
const formName = str(settings, "formName");
|
|
2160
|
+
const submit = bool(settings, "showSubmit") ? '<div class="ptc-form-actions"><button type="submit" class="ptc-form-submit">' + (SUBMIT_ICONS[str(settings, "submitIcon")] ?? "") + `${esc(str(settings, "submitText"))}</button></div>` : "";
|
|
2161
|
+
const footer = str(settings, "footerText");
|
|
2162
|
+
const success = str(settings, "successMessage");
|
|
2163
|
+
return `<div class="ptc-form" data-label-position="${escAttr(str(settings, "labelPosition"))}">` + nav + `<form class="ptc-form-body" id="${id}-form"` + (formName ? ` data-form-name="${escAttr(formName)}"` : "") + (action ? ` action="${escAttr(action)}" method="post"` : "") + `>${body}${submit}</form>` + // richtext:原样插入(页脚正是隐私政策链接的所在)。
|
|
2164
|
+
(footer ? `<div class="ptc-form-footer">${footer}</div>` : "") + `<div class="ptc-form-success" role="status" hidden>${esc(success)}</div></div>`;
|
|
2165
|
+
},
|
|
2166
|
+
css() {
|
|
2167
|
+
return [
|
|
2168
|
+
".ptc-form{width:100%}",
|
|
2169
|
+
".ptc-form-body{display:flex;flex-wrap:wrap;gap:calc(var(--spacing)*4)}",
|
|
2170
|
+
".ptc-form-step{display:flex;flex-wrap:wrap;gap:calc(var(--spacing)*4);width:100%}",
|
|
2171
|
+
".ptc-form-field{flex:1 1 100%;min-width:0;margin:0;padding:0;border:0}",
|
|
2172
|
+
'.ptc-form-field[data-width="half"]{flex:1 1 calc(50% - var(--spacing)*2)}',
|
|
2173
|
+
".ptc-form-label{display:block;margin-bottom:calc(var(--spacing)*1.5);font-weight:500}",
|
|
2174
|
+
".ptc-form-note{width:100%}",
|
|
2175
|
+
".ptc-form-input{width:100%;box-sizing:border-box;font:inherit;padding:calc(var(--spacing)*2.5) calc(var(--spacing)*3.5);border:1px solid var(--pt-form-field-border,color-mix(in oklab,currentColor 25%,transparent));border-radius:var(--radius-md);background:var(--pt-form-field-bg,transparent);color:var(--pt-form-field-fg,inherit)}",
|
|
2176
|
+
"textarea.ptc-form-input{min-height:6em;resize:vertical}",
|
|
2177
|
+
".ptc-form-choices{display:flex;flex-wrap:wrap;gap:calc(var(--spacing)*4)}",
|
|
2178
|
+
".ptc-form-choice{display:inline-flex;align-items:center;gap:calc(var(--spacing)*1.5)}",
|
|
2179
|
+
".ptc-form-star{color:var(--pt-form-required-fg,currentColor)}",
|
|
2180
|
+
".ptc-form-badge{margin-left:calc(var(--spacing)*1.5);padding:0 calc(var(--spacing)*1.5);border-radius:var(--radius-xs);font-size:.75em;vertical-align:middle;background:var(--pt-form-required-fg,currentColor);color:var(--pt-form-on-accent,white)}",
|
|
2181
|
+
".ptc-form-actions{width:100%}",
|
|
2182
|
+
// 手机满宽(拇指点击区),md 起自适应 —— submitFullWidth 那个开关的继任者。
|
|
2183
|
+
".ptc-form-submit{width:100%;font:inherit;font-weight:600;cursor:pointer;padding:calc(var(--spacing)*3) calc(var(--spacing)*7);border:0;border-radius:var(--radius-md);background:var(--pt-form-accent,currentColor);color:var(--pt-form-on-accent,white)}",
|
|
2184
|
+
"@media (min-width:768px){.ptc-form-submit{width:auto}}",
|
|
2185
|
+
".ptc-form-steps{display:flex;gap:calc(var(--spacing)*1.5);margin-bottom:calc(var(--spacing)*4)}",
|
|
2186
|
+
".ptc-form-step-tab{flex:1;font:inherit;cursor:pointer;padding:calc(var(--spacing)*2) calc(var(--spacing)*1);border:0;border-radius:var(--radius-md);background:color-mix(in oklab,currentColor 10%,transparent)}",
|
|
2187
|
+
'.ptc-form-step-tab[aria-selected="true"]{background:var(--pt-form-accent,currentColor);color:var(--pt-form-on-accent,white)}',
|
|
2188
|
+
".ptc-form-footer{margin-top:calc(var(--spacing)*3);font-size:.875em;color:color-mix(in oklab,currentColor 65%,transparent)}",
|
|
2189
|
+
".ptc-form-success{margin-top:calc(var(--spacing)*3)}",
|
|
2190
|
+
// labelPosition=inline:标签与控件并排。
|
|
2191
|
+
'[data-label-position="inline"] .ptc-form-field{display:flex;align-items:center;gap:calc(var(--spacing)*3)}',
|
|
2192
|
+
'[data-label-position="inline"] .ptc-form-label{flex:0 0 auto;margin-bottom:0}'
|
|
2193
|
+
].join("");
|
|
2194
|
+
},
|
|
2195
|
+
js() {
|
|
2196
|
+
return componentScript(
|
|
2197
|
+
"form",
|
|
2198
|
+
// 分步:与 tabs 同款,只翻 aria-selected / hidden。
|
|
2199
|
+
`root.addEventListener('click',function(e){var t=e.target,b=t&&t.closest?t.closest("[data-step-to]"):null;if(!b||!root.contains(b))return;var i=b.getAttribute('data-step-to');var tb=root.querySelectorAll("[data-step-to]"),pn=root.querySelectorAll("[data-step]");for(var k=0;k<tb.length;k++)tb[k].setAttribute('aria-selected',tb[k]===b?'true':'false');for(var j=0;j<pn.length;j++){if(pn[j].getAttribute('data-step')===i)pn[j].removeAttribute('hidden');else pn[j].setAttribute('hidden','');}});root.addEventListener('submit',function(e){var f=e.target;if(!f||f.tagName!=='FORM')return;var d={};new FormData(f).forEach(function(v,k){d[k]=v;});f.dispatchEvent(new CustomEvent('pt-form-submit',{detail:d,bubbles:true,composed:true}));if(!f.getAttribute('action')){e.preventDefault();var s=root.querySelector('.ptc-form-success');if(s)s.removeAttribute('hidden');}});`
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
};
|
|
2203
|
+
|
|
2204
|
+
// src/components/renderers/stickyCta.ts
|
|
2205
|
+
var BAR_HEIGHT = "var(--pt-sticky-cta-h,3.5rem)";
|
|
2206
|
+
var RESERVE = `calc(${BAR_HEIGHT} + env(safe-area-inset-bottom,0px))`;
|
|
2207
|
+
var stickyCtaRenderer = {
|
|
2208
|
+
html(instance) {
|
|
2209
|
+
const settings = instance.settings;
|
|
2210
|
+
const text = str(settings, "text");
|
|
2211
|
+
const buttonText = str(settings, "buttonText");
|
|
2212
|
+
const showAfter = num(settings, "showAfter");
|
|
2213
|
+
return `<div class="ptc-sticky" data-sticky-on="${escAttr(str(settings, "showOn"))}" data-sticky-align="${escAttr(str(settings, "align"))}" data-show-after="${showAfter}"` + // armed = 交给脚本管可见性。只有 showAfter > 0 才有这回事。
|
|
2214
|
+
(showAfter > 0 ? " data-armed" : "") + ">" + (text ? `<span class="ptc-sticky-text">${esc(text)}</span>` : "") + (buttonText ? `<a class="ptc-sticky-btn" href="${escAttr(str(settings, "buttonUrl"))}">${esc(buttonText)}</a>` : "") + "</div>";
|
|
2215
|
+
},
|
|
2216
|
+
css() {
|
|
2217
|
+
return [
|
|
2218
|
+
`.ptc-sticky{position:fixed;left:0;right:0;bottom:0;z-index:50;box-sizing:border-box;display:flex;align-items:center;gap:calc(var(--spacing)*3);min-height:${BAR_HEIGHT};padding:calc(var(--spacing)*2) calc(var(--spacing)*4);padding-bottom:calc(var(--spacing)*2 + env(safe-area-inset-bottom,0px));background:var(--pt-sticky-cta-bg,canvas);color:var(--pt-sticky-cta-fg,canvastext);border-top:1px solid var(--pt-sticky-cta-border,color-mix(in oklab,currentColor 15%,transparent))}`,
|
|
2219
|
+
'.ptc-sticky[data-sticky-align="center"]{justify-content:center}',
|
|
2220
|
+
'.ptc-sticky[data-sticky-align="between"]{justify-content:space-between}',
|
|
2221
|
+
".ptc-sticky-text{min-width:0}",
|
|
2222
|
+
".ptc-sticky-btn{flex:none;display:inline-block;padding:calc(var(--spacing)*2.5) calc(var(--spacing)*6);border-radius:var(--radius-lg);text-decoration:none;font-weight:600;background:var(--pt-sticky-cta-accent,currentColor);color:var(--pt-sticky-cta-on-accent,canvas)}",
|
|
2223
|
+
// 滚够了才出现:初始 armed,脚本摘掉它。
|
|
2224
|
+
".ptc-sticky[data-armed]{display:none}",
|
|
2225
|
+
// PC 隐藏是 showOn=mobile 的默认档,不是写死的行为。
|
|
2226
|
+
'@media (min-width:768px){.ptc-sticky[data-sticky-on="mobile"]{display:none}}',
|
|
2227
|
+
// 底部留白。见文件头:只能由 body 腾,且不写重置规则。
|
|
2228
|
+
`body:has(.ptc-sticky[data-sticky-on="all"]){padding-bottom:${RESERVE}}`,
|
|
2229
|
+
`@media (max-width:767.98px){body:has(.ptc-sticky[data-sticky-on="mobile"]){padding-bottom:${RESERVE}}}`
|
|
2230
|
+
].join("");
|
|
2231
|
+
},
|
|
2232
|
+
js() {
|
|
2233
|
+
return componentScript(
|
|
2234
|
+
"sticky-cta",
|
|
2235
|
+
"var b=root.querySelector('.ptc-sticky');if(!b)return;var a=parseInt(b.getAttribute('data-show-after'),10);if(!(a>0))return;function u(){if((window.pageYOffset||0)>=a)b.removeAttribute('data-armed');else b.setAttribute('data-armed','');}window.addEventListener('scroll',u,{passive:true});u();"
|
|
2236
|
+
);
|
|
2237
|
+
}
|
|
2238
|
+
};
|
|
2239
|
+
|
|
2240
|
+
// src/components/renderers/tabs.ts
|
|
2241
|
+
var tabsRenderer = {
|
|
2242
|
+
html(instance, instanceId) {
|
|
2243
|
+
const style = str(instance.settings, "tabStyle");
|
|
2244
|
+
const title = str(instance.settings, "title");
|
|
2245
|
+
const tabs = instance.blocks.filter((b) => b.type === "tab");
|
|
2246
|
+
const wanted = num(instance.settings, "defaultTab");
|
|
2247
|
+
const active = Number.isInteger(wanted) && wanted >= 0 && wanted < tabs.length ? wanted : 0;
|
|
2248
|
+
const id = escAttr(instanceId);
|
|
2249
|
+
const nav = tabs.map((tab, i) => {
|
|
2250
|
+
const on = i === active;
|
|
2251
|
+
const icon2 = str(tab.settings, "icon");
|
|
2252
|
+
const label = (icon2 ? `${esc(icon2)} ` : "") + esc(str(tab.settings, "title"));
|
|
2253
|
+
return `<button type="button" class="ptc-tabs-tab" role="tab" id="${id}-t${i}" aria-controls="${id}-p${i}" aria-selected="${on ? "true" : "false"}" tabindex="${on ? "0" : "-1"}">${label}</button>`;
|
|
2254
|
+
}).join("");
|
|
2255
|
+
const panels = tabs.map(
|
|
2256
|
+
(tab, i) => `<div class="ptc-tabs-panel" role="tabpanel" id="${id}-p${i}" aria-labelledby="${id}-t${i}"${i === active ? "" : " hidden"}>${str(tab.settings, "content")}</div>`
|
|
2257
|
+
).join("");
|
|
2258
|
+
const head = title ? `<h3 class="ptc-tabs-title">${esc(title)}</h3>` : "";
|
|
2259
|
+
return `<div class="ptc-tabs" data-tabs-style="${escAttr(style)}">` + head + `<div class="ptc-tabs-nav" role="tablist">${nav}</div><div class="ptc-tabs-panels">${panels}</div></div>`;
|
|
2260
|
+
},
|
|
2261
|
+
css() {
|
|
2262
|
+
return [
|
|
2263
|
+
".ptc-tabs{width:100%}",
|
|
2264
|
+
".ptc-tabs-title{margin:0 0 calc(var(--spacing)*4);font-size:1.25em;font-weight:700}",
|
|
2265
|
+
".ptc-tabs-nav{display:flex;flex-wrap:wrap;gap:calc(var(--spacing)*1)}",
|
|
2266
|
+
".ptc-tabs-tab{appearance:none;border:0;background:none;font:inherit;cursor:pointer;padding:calc(var(--spacing)*2) calc(var(--spacing)*4);color:color-mix(in oklab,currentColor 60%,transparent)}",
|
|
2267
|
+
// 激活态只由 aria-selected 决定 —— 脚本翻的就是它。
|
|
2268
|
+
'.ptc-tabs-tab[aria-selected="true"]{color:var(--pt-tabs-accent,currentColor)}',
|
|
2269
|
+
".ptc-tabs-panel{padding-block:calc(var(--spacing)*5)}",
|
|
2270
|
+
// underline
|
|
2271
|
+
'[data-tabs-style="underline"] .ptc-tabs-nav{border-bottom:1px solid var(--pt-tabs-border,color-mix(in oklab,currentColor 15%,transparent))}',
|
|
2272
|
+
'[data-tabs-style="underline"] .ptc-tabs-tab{border-bottom:2px solid transparent;margin-bottom:-1px}',
|
|
2273
|
+
'[data-tabs-style="underline"] .ptc-tabs-tab[aria-selected="true"]{border-bottom-color:var(--pt-tabs-accent,currentColor)}',
|
|
2274
|
+
// pill
|
|
2275
|
+
'[data-tabs-style="pill"] .ptc-tabs-tab{border-radius:var(--radius-xl)}',
|
|
2276
|
+
'[data-tabs-style="pill"] .ptc-tabs-tab[aria-selected="true"]{background:var(--pt-tabs-accent,currentColor);color:var(--pt-tabs-on-accent,white)}',
|
|
2277
|
+
// boxed —— v2 的 'box' 是笔误,这一档在 v2 里根本进不去
|
|
2278
|
+
'[data-tabs-style="boxed"] .ptc-tabs-tab{border:1px solid transparent;border-radius:var(--radius-md)}',
|
|
2279
|
+
'[data-tabs-style="boxed"] .ptc-tabs-tab[aria-selected="true"]{border-color:var(--pt-tabs-border,color-mix(in oklab,currentColor 15%,transparent))}'
|
|
2280
|
+
].join("");
|
|
2281
|
+
},
|
|
2282
|
+
js() {
|
|
2283
|
+
return componentScript(
|
|
2284
|
+
"tabs",
|
|
2285
|
+
`root.addEventListener('click',function(e){var t=e.target;var b=t&&t.closest?t.closest('[role="tab"]'):null;if(!b||!root.contains(b))return;var tb=root.querySelectorAll('[role="tab"]'),pn=root.querySelectorAll('[role="tabpanel"]');for(var i=0;i<tb.length;i++){var on=tb[i]===b;tb[i].setAttribute('aria-selected',on?'true':'false');tb[i].setAttribute('tabindex',on?'0':'-1');if(pn[i]){if(on)pn[i].removeAttribute('hidden');else pn[i].setAttribute('hidden','');}}});`
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
};
|
|
2289
|
+
|
|
2290
|
+
// src/components/render.ts
|
|
2291
|
+
var RENDERERS = {
|
|
2292
|
+
carousel: carouselRenderer,
|
|
2293
|
+
faq: faqRenderer,
|
|
2294
|
+
form: formRenderer,
|
|
2295
|
+
tabs: tabsRenderer,
|
|
2296
|
+
"sticky-cta": stickyCtaRenderer
|
|
2297
|
+
};
|
|
2298
|
+
function rendererOf(type) {
|
|
2299
|
+
const renderer = RENDERERS[type];
|
|
2300
|
+
if (!renderer) throw editorError("COMPONENT_UNKNOWN", { params: { type } });
|
|
2301
|
+
return renderer;
|
|
2302
|
+
}
|
|
2303
|
+
function renderHtml(type, settings, instanceId) {
|
|
2304
|
+
const renderer = rendererOf(type);
|
|
2305
|
+
const contract = COMPONENT_CONTRACTS[type];
|
|
2306
|
+
try {
|
|
2307
|
+
return renderer.html(resolveComponent(contract, settings), instanceId);
|
|
2308
|
+
} catch (err) {
|
|
2309
|
+
asRenderError(type, err);
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
function renderCss(type) {
|
|
2313
|
+
const renderer = rendererOf(type);
|
|
2314
|
+
try {
|
|
2315
|
+
return renderer.css();
|
|
2316
|
+
} catch (err) {
|
|
2317
|
+
asRenderError(type, err);
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
function renderJs(type) {
|
|
2321
|
+
const renderer = rendererOf(type);
|
|
2322
|
+
try {
|
|
2323
|
+
return renderer.js();
|
|
2324
|
+
} catch (err) {
|
|
2325
|
+
asRenderError(type, err);
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
function asRenderError(type, err) {
|
|
2329
|
+
if (typeof err === "object" && err !== null && isErrorCode(err.code)) {
|
|
2330
|
+
throw err;
|
|
2331
|
+
}
|
|
2332
|
+
throw editorError("COMPONENT_RENDER_FAILED", { params: { type } });
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
// src/components/contract.ts
|
|
2336
|
+
var SETTING_TYPES = [
|
|
2337
|
+
"text",
|
|
2338
|
+
"richtext",
|
|
2339
|
+
"number",
|
|
2340
|
+
"boolean",
|
|
2341
|
+
"enum",
|
|
2342
|
+
"url",
|
|
2343
|
+
"image",
|
|
2344
|
+
"list"
|
|
2345
|
+
];
|
|
2346
|
+
|
|
1385
2347
|
// src/collectClasses.ts
|
|
1386
2348
|
function collectClasses(root) {
|
|
1387
2349
|
const out = /* @__PURE__ */ new Set();
|
|
@@ -1407,20 +2369,22 @@ function createCore(options) {
|
|
|
1407
2369
|
return {
|
|
1408
2370
|
parse,
|
|
1409
2371
|
parseFragment,
|
|
2372
|
+
wrap: createWrap(parse),
|
|
2373
|
+
unwrap,
|
|
1410
2374
|
serialize,
|
|
1411
2375
|
normalize,
|
|
1412
2376
|
validate,
|
|
1413
2377
|
apply,
|
|
1414
2378
|
guards: guards_exports,
|
|
1415
2379
|
compile: compile_exports,
|
|
2380
|
+
components: components_exports,
|
|
1416
2381
|
collectClasses,
|
|
1417
2382
|
rules,
|
|
1418
|
-
KIND_PROFILES,
|
|
1419
2383
|
VERSION,
|
|
1420
2384
|
COMPILER_VERSION
|
|
1421
2385
|
};
|
|
1422
2386
|
}
|
|
1423
2387
|
|
|
1424
|
-
export { COMPILER_VERSION, EMPTY_THEME, ERROR_CODES,
|
|
2388
|
+
export { COMPILER_VERSION, EMPTY_THEME, ERROR_CODES, MAX_CONTAINER_DEPTH, PUBLISH_BLOCKING_RULES, VERSION, apply, canContain, checkVariants, collectClasses, compile_exports as compile, components_exports as components, containerDepth, createCore, deleteTarget, editorError, fragmentElementRoots, guards_exports as guards, isErrorCode, isNativePaletteClass, isPublishBlocking, isWrapLayer, layerOf, normalize, normalizeClassValue, parseFragment, rules, serialize, themeFromSource, themeOf, themeTokens, unwrap, validate };
|
|
1425
2389
|
//# sourceMappingURL=index.js.map
|
|
1426
2390
|
//# sourceMappingURL=index.js.map
|