@ptengine/lp-editor-core 1.0.0-alpha.3 → 1.0.0-alpha.5

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.
@@ -39,6 +39,8 @@ const core = await import(join(here, '..', 'index.js'));
39
39
  const manifest = JSON.parse(readFileSync(join(here, 'manifest.json'), 'utf8'));
40
40
  const opsData = JSON.parse(readFileSync(join(here, 'ops-cases.json'), 'utf8'));
41
41
  const opsByName = new Map(opsData.cases.map(c => [c.name, c]));
42
+ const componentData = JSON.parse(readFileSync(join(here, 'component-cases.json'), 'utf8'));
43
+ const componentByName = new Map(componentData.cases.map(c => [c.name, c]));
42
44
 
43
45
  const DOMParser = new JSDOM('').window.DOMParser;
44
46
  const { parse, wrap } = core.createCore({ DOMParser });
@@ -77,6 +79,16 @@ function compute(entry) {
77
79
  }
78
80
  return core.serialize(doc);
79
81
  }
82
+ // 组件三件产物。css / js 的「输入」就是类型 id(它们不吃设置),
83
+ // html 的输入是取样文件里的一条用例。
84
+ if (entry.kind === 'component-css') return core.components.renderCss(entry.input.split('#')[1]);
85
+ if (entry.kind === 'component-js') return core.components.renderJs(entry.input.split('#')[1]);
86
+ if (entry.kind === 'component-html') {
87
+ const name = entry.input.split('#')[1];
88
+ const c = componentByName.get(name);
89
+ if (!c) throw new Error(`component-cases.json 里找不到用例 ${name}`);
90
+ return core.components.renderHtml(c.type, c.settings, c.id);
91
+ }
80
92
  if (entry.kind === 'apply') {
81
93
  const name = entry.input.split('#')[1];
82
94
  const c = opsByName.get(name);
package/dist/index.d.ts CHANGED
@@ -17,45 +17,6 @@ declare function parseFragment(doc: Document, html: string): DocumentFragment;
17
17
  /** 片段里的元素子节点(跳过纯空白文本),供单根校验使用。 */
18
18
  declare function fragmentElementRoots(fragment: DocumentFragment): Element[];
19
19
 
20
- /**
21
- * 规范序列化(canonical serializer)。
22
- *
23
- * 目标:同一棵 DOM 树(由任何符合 HTML5 规范的树构造器产出),无论宿主是浏览器
24
- * 原生 DOM 还是 jsdom,都序列化成**逐字节相同**的字符串。这是「编辑器 apply ==
25
- * mastra apply」那条不变量的落点。
26
- *
27
- * 约束:
28
- * - 纯函数,无副作用、无 I/O
29
- * - 只依赖标准 DOM 读 API,同一份实现在浏览器与 Node 两侧跑
30
- * - 绝不使用 innerHTML / outerHTML —— 它们本身就是分歧的来源
31
- *
32
- * 规范规则(全部写死,不提供选项):
33
- * R1 标签名 HTML 命名空间元素小写;外来元素(SVG / MathML)保留
34
- * 解析器调整后的 localName(foreignObject、clipPath…)
35
- * R2 属性顺序 按限定名排序,UTF-16 码元序
36
- * R3 属性引号 一律 name="value",双引号,永远带引号
37
- * R4 文本转义 & < > -> &amp; &lt; &gt;
38
- * R5 属性值转义 & < > " -> &amp; &lt; &gt; &quot;
39
- * R6 空元素 <br> —— 不带结尾斜杠、不带闭合标签
40
- * R7 pre/textarea/listing 空白原样保留;紧随起始标签的换行重新发出
41
- * (解析器会吃掉一个),保证再解析回同一棵树
42
- * R8 注释 原样 <!--data-->
43
- * R9 doctype 规范化;无 id 的 html -> <!DOCTYPE html>
44
- * R10 布尔/空属性 统一一种形态:disabled=""、alt=""
45
- * R11 raw text script/style/xmp/iframe/noembed/noframes/plaintext
46
- * 的字符数据原样输出,不转义
47
- * R12 template 经其 .content 片段序列化
48
- * R13 回车 U+000D 在文本与属性值里输出为 &#13;,使输出可往返
49
- * R14 class 值 按空白切分后**去重**、单空格拼接,**不重排顺序**
50
- *
51
- * R13 与 R14 是相对 WHATWG 序列化器的**增补**,不是放宽:
52
- * - R13:词法分析器在建树前把 CR / CRLF 归一成 LF,不转义的 CR 会在下一次解析时
53
- * 静默变成 LF。原生 outerHTML 在所有引擎上都有这个毛病。
54
- * - R14:两侧按同一组 op 应用会得到同样的插入顺序,所以顺序天然确定、不需要排序;
55
- * 但源数据里的 `class="a b a"` 这类写法会让字节不同。去重 + 折叠空白把它收敛掉。
56
- * **有意不排序** —— 排序会把作者写的顺序变成非作者的,而 `md:` 与基线的相对位置
57
- * 在读源码时是有意义的。
58
- */
59
20
  /**
60
21
  * R14:class 值去重 + 折叠空白,保持首次出现的顺序。
61
22
  * 导出是因为 EditOp 的 class 增删也要用同一份规则,否则「写进去的」与
@@ -143,7 +104,7 @@ declare function unwrap(doc: Document): DocumentFragment;
143
104
  * iam 发布拒绝按 `rule` 出后端文案,mastra 回给模型的结构化报错复用同一枚举。
144
105
  * 三处各一份措辞,但没有两份真源 —— 这也是 core 能作为公开包发布的前提之一。
145
106
  */
146
- declare const ERROR_CODES: readonly ["FORMAT_MISSING", "ID_DUPLICATE", "ID_CONFLICT", "ID_FORMAT", "ID_NOT_FOUND", "SCRIPT_FORBIDDEN", "INLINE_STYLE", "NATIVE_PALETTE", "UNIT_FORBIDDEN", "CLASS_UNCOMPILABLE", "VARIANT_FORBIDDEN", "STRUCTURE", "MULTI_ROOT", "REPLACE_ID_CHANGED", "STALE_BASE_VERSION", "FRAGMENT_NOT_CANONICAL", "PLACEHOLDER_UNRESOLVED"];
107
+ declare const ERROR_CODES: readonly ["FORMAT_MISSING", "ID_DUPLICATE", "ID_CONFLICT", "ID_FORMAT", "ID_NOT_FOUND", "SCRIPT_FORBIDDEN", "INLINE_STYLE", "NATIVE_PALETTE", "UNIT_FORBIDDEN", "CLASS_UNCOMPILABLE", "VARIANT_FORBIDDEN", "STRUCTURE", "MULTI_ROOT", "REPLACE_ID_CHANGED", "STALE_BASE_VERSION", "FRAGMENT_NOT_CANONICAL", "PLACEHOLDER_UNRESOLVED", "COMPONENT_UNKNOWN", "COMPONENT_RENDER_FAILED", "COMPONENT_SETTING_UNKNOWN", "COMPONENT_SETTING_INVALID"];
147
108
  type ErrorCode = (typeof ERROR_CODES)[number];
148
109
  declare function isErrorCode(value: unknown): value is ErrorCode;
149
110
  /** 校验条目。**不带 message** —— 文案归消费端。 */
@@ -198,6 +159,20 @@ interface ValidateOptions {
198
159
  * 不传则跳过这条规则 —— 用于只想跑结构 / 格式检查的场合。
199
160
  */
200
161
  canCompileClass?: (className: string) => boolean;
162
+ /**
163
+ * 只允许 core 契约里有的组件类型。默认**不限**。
164
+ *
165
+ * 为什么默认宽松:core 不感知宿主(弹窗那条路就有自己的 `countdown`,归宿主
166
+ * adapter),而「契约里没有的类型」在渲染那一侧已经很响了 —— `renderHtml` 直接抛
167
+ * `COMPONENT_UNKNOWN`,成品拼装会整体失败。不需要 validate 再拦一道。
168
+ *
169
+ * 对比之下,未知**字段**是静默的(渲染器按默认值渲染、改了没反应),所以那一条
170
+ * 无条件报,不受这个开关影响。两者的响度不同,处理也就不同。
171
+ *
172
+ * 传 `true` 的场合:调用方掌握着完整的组件词表(比如落地页编辑器),想在发布前
173
+ * 就把「AI 编了一个谁也不认识的组件」挡下来。
174
+ */
175
+ knownComponentsOnly?: boolean;
201
176
  }
202
177
  interface ValidateResult {
203
178
  ok: boolean;
@@ -400,6 +375,379 @@ declare namespace compile {
400
375
  export { compile_EMPTY_THEME as EMPTY_THEME, type compile_Theme as Theme, type compile_ThemeTokens as ThemeTokens, compile_canCompile as canCompile, compile_clearCompileCache as clearCompileCache, compile_fresh as fresh, compile_incremental as incremental, compile_mergeClasses as mergeClasses, compile_themeFromSource as themeFromSource, compile_themeOf as themeOf, compile_themeTokens as themeTokens, compile_warm as warm };
401
376
  }
402
377
 
378
+ /**
379
+ * 内置组件的**字段契约形状**。这里只有类型,五件的契约数据在 `contracts.ts`。
380
+ *
381
+ * 契约里只放:字段 id / 取值类型 / 枚举取值 / 默认值 / 块定义(design D3)。
382
+ *
383
+ * **不放任何文案**,也不放任何面向界面的元信息 —— 显示名、分组、占位提示、预设留
384
+ * apps/x 的面板,AI 语义提示留 mastra,两边按字段 id 引用。理由不是洁癖:core 是
385
+ * 公开 npm 包,装进 UI 文案就得装三语,于是又多一份三语字典,与「core 只出结构、
386
+ * 不出文案」的既有口径(见 `errors.ts`)直接冲突。
387
+ *
388
+ * 这条由 `__tests__/contract.spec.ts` 的属性名锁定测试兜住 —— 加一个 `label` 就红。
389
+ *
390
+ * ---
391
+ *
392
+ * **为什么是可辨识联合而不是一个带可选字段的接口**:`values` 只对枚举有意义,
393
+ * `default` 的类型跟着 `type` 走。写成 `{ type: SettingType; values?: string[];
394
+ * default?: unknown }` 的话,「number 字段配了一组 values」「boolean 字段的默认值是
395
+ * 字符串」这类错误要等到 validate 运行时才发现,而它们本该在写契约那一刻就编译不过。
396
+ *
397
+ * **有意不含 `min` / `max`**:D3 枚举的五项里没有它,tasks 4.2 的三条校验
398
+ * (未知字段 / 类型不符 / 枚举越界)也用不到。契约里放一个没有消费端的字段,
399
+ * 正是 D3 要堵的那种「写了没反应」。
400
+ *
401
+ * **有意不含 `maxBlocks`**:同上,且它是面板的「还能不能再加一块」这类界面行为。
402
+ * 若 1.3 落契约时发现确实需要,那是一次要被看见的契约扩张,不是顺手加。
403
+ */
404
+ /**
405
+ * 取值类型词表。
406
+ *
407
+ * `text` 与 `richtext` 的区别是**转义行为**,不是界面控件:`text` 在 renderHtml 里
408
+ * 必须转义,`richtext` 原样插入。v2 就是这么做的(问题转义、答案不转义),
409
+ * 但那个区别只存在于渲染器代码里、没有进 schema —— 于是面板与 AI 都看不见它。
410
+ * 它关乎注入安全,属于契约。
411
+ *
412
+ * `url` 与 `image` 都是字符串,分开是因为取值域不同(`image` 指向图片资源),
413
+ * 消费端据此选控件、AI 据此知道该产出什么。这是取值类型,不是显示名。
414
+ *
415
+ * `list` 是字符串数组。v2 把「一行一项」的选项表存成换行分隔的字符串,渲染器得用
416
+ * `/\r?\n|\\n/` 去拆 —— 那个 `\\n` 分支的存在本身就说明 AI 写出过字面量反斜杠 n。
417
+ * 结构化成数组,这类歧义整类消失。
418
+ */
419
+ declare const SETTING_TYPES: readonly ["text", "richtext", "number", "boolean", "enum", "url", "image", "list"];
420
+ type SettingType = (typeof SETTING_TYPES)[number];
421
+ /** 一个设置项的合法取值。与 `SETTING_TYPES` 一一对应。 */
422
+ type SettingValue = string | number | boolean | readonly string[];
423
+ interface TextSettingSpec {
424
+ readonly id: string;
425
+ /** 纯文本。renderHtml **必须**转义。 */
426
+ readonly type: 'text';
427
+ readonly default: string;
428
+ }
429
+ interface RichTextSettingSpec {
430
+ readonly id: string;
431
+ /** 富文本(HTML 片段)。renderHtml 原样插入,**不**转义。 */
432
+ readonly type: 'richtext';
433
+ readonly default: string;
434
+ }
435
+ interface UrlSettingSpec {
436
+ readonly id: string;
437
+ readonly type: 'url';
438
+ readonly default: string;
439
+ }
440
+ interface ImageSettingSpec {
441
+ readonly id: string;
442
+ readonly type: 'image';
443
+ readonly default: string;
444
+ }
445
+ interface NumberSettingSpec {
446
+ readonly id: string;
447
+ readonly type: 'number';
448
+ readonly default: number;
449
+ }
450
+ interface BooleanSettingSpec {
451
+ readonly id: string;
452
+ readonly type: 'boolean';
453
+ readonly default: boolean;
454
+ }
455
+ interface EnumSettingSpec {
456
+ readonly id: string;
457
+ readonly type: 'enum';
458
+ /** 允许的取值。`default` 必须是其中之一 —— 由契约的锁定测试断言。 */
459
+ readonly values: readonly string[];
460
+ readonly default: string;
461
+ }
462
+ interface ListSettingSpec {
463
+ readonly id: string;
464
+ readonly type: 'list';
465
+ readonly default: readonly string[];
466
+ }
467
+ /** 一个设置项。按 `type` 辨识。 */
468
+ type ComponentSettingSpec = TextSettingSpec | RichTextSettingSpec | UrlSettingSpec | ImageSettingSpec | NumberSettingSpec | BooleanSettingSpec | EnumSettingSpec | ListSettingSpec;
469
+ /**
470
+ * 可重复块的定义(FAQ 的一问一答、轮播的一张、表单的一个字段、tabs 的一页)。
471
+ *
472
+ * `type` 是块的**类型 id**(`item` / `slide` / `field` / `tab`),落在实例设置的
473
+ * `_blocks[].type` 上,不是显示名。
474
+ */
475
+ interface ComponentBlockSpec {
476
+ readonly type: string;
477
+ readonly fields: readonly ComponentSettingSpec[];
478
+ }
479
+ /** 一件组件的完整字段契约。 */
480
+ interface ComponentContract {
481
+ /** 组件类型 id,落在 `data-pt-component` 上。 */
482
+ readonly type: string;
483
+ readonly fields: readonly ComponentSettingSpec[];
484
+ /** 没有可重复块的组件(如 sticky-cta)不给这一项。 */
485
+ readonly blocks?: readonly ComponentBlockSpec[];
486
+ }
487
+
488
+ /**
489
+ * 一份**实例设置**的形状 —— 与契约形状(`contract.ts`)分开。
490
+ *
491
+ * 两者是一个契约的两端:契约说「这件组件有哪些字段、各是什么类型」,实例设置是
492
+ * 某个声明节点上 `data-pt-component-settings` 里实际写着的东西。
493
+ *
494
+ * 分成两个模块不是为了整齐:`contract.ts` 上挂着一条属性名锁定测试(「契约里不得
495
+ * 出现任何文案或界面元信息」),而实例设置这边的 `settings` 是个正当属性名。
496
+ * 放在一起会逼那条锁定测试放宽,护栏就钝了。
497
+ */
498
+ /** 一份实例设置。`_blocks` 之外的键都是设置项。 */
499
+ type ComponentSettings = Readonly<Record<string, unknown>>;
500
+ /** 实例设置里的一块。 */
501
+ interface ComponentBlockValue {
502
+ readonly type: string;
503
+ readonly settings: ComponentSettings;
504
+ }
505
+ /** `_blocks` 在实例设置里的键名。与 v2 一致,存量声明直接可读。 */
506
+ declare const BLOCKS_KEY = "_blocks";
507
+
508
+ /**
509
+ * 从字段契约解析出一份**完整且类型正确**的设置(tasks 1.4)。
510
+ *
511
+ * 合并顺序:契约默认值 ← 实例 settings。渲染器拿到的永远是每个契约字段都在场、
512
+ * 且类型与契约一致的值 —— 所以 2.x 的五个渲染器里**不需要**再写 v2 那套
513
+ * `str(settings, 'x', '默认')` / `num(settings, 'y', 16)` 兜底。那套兜底正是 v2
514
+ * 事故的温床:同一个字段的默认值在 schema 与渲染器里各写一份,然后悄悄漂移
515
+ * (`borderRadius` 面板显示 0、实际渲染 8;`labelFontWeight` 产出 `NaN`)。
516
+ * 默认值只有契约一处,是这个模块存在的全部理由。
517
+ *
518
+ * ---
519
+ *
520
+ * **三类脏输入一律回落到契约默认值,不抛错、不强转**:
521
+ *
522
+ * | 输入 | 这里 | 谁去告诉作者 |
523
+ * | --- | --- | --- |
524
+ * | 契约里没有的字段 | 丢掉 | `validate` 的 `COMPONENT_SETTING_UNKNOWN`(4.2) |
525
+ * | 类型不符(`interval: '5000'`) | 回落默认值 | `COMPONENT_SETTING_INVALID`(4.2) |
526
+ * | 枚举越界(`tabStyle: 'box'`) | 回落默认值 | 同上 |
527
+ *
528
+ * 分工是刻意的:**解析保证渲染器安全,校验保证作者看得见。** 两件事合成一件的话,
529
+ * 要么渲染器得处理脏值(回到 v2),要么脏值会打断整份渲染(一个拼错的字段名让整页
530
+ * 导不出去)。
531
+ *
532
+ * 不强转也是刻意的:`Number('5000')` 看着无害,但 `Number('')` 是 0、
533
+ * `Number('abc')` 是 `NaN`,而 `NaN` 会一路渲进 markup —— v2 的 `labelFontWeight`
534
+ * 就是这么产出 `font-weight:NaN` 的。回落到一个契约写明的值,至少是可预期的。
535
+ */
536
+
537
+ /** 一份解析完的设置:契约里每个字段都在场,且类型与契约一致。 */
538
+ type ResolvedSettings = Readonly<Record<string, SettingValue>>;
539
+ /** 解析完的一块。 */
540
+ interface ResolvedBlock {
541
+ readonly type: string;
542
+ readonly settings: ResolvedSettings;
543
+ }
544
+ /** 解析完的一件组件实例。 */
545
+ interface ResolvedComponent {
546
+ readonly type: string;
547
+ readonly settings: ResolvedSettings;
548
+ /** 契约没有块定义时恒为空数组 —— 渲染器不必判空。 */
549
+ readonly blocks: readonly ResolvedBlock[];
550
+ }
551
+ /**
552
+ * 把一份实例设置解析成渲染器可以直接用的形状。
553
+ *
554
+ * 契约里没有块定义、或实例没写 `_blocks` 时,`blocks` 是空数组 —— 渲染器不必判空,
555
+ * 也就不会出现「某个渲染器忘了判空于是整页导出炸掉」。
556
+ *
557
+ * 类型不认识的块(`_blocks` 里写着契约没有的 `type`)整块丢掉,与未知字段同一口径:
558
+ * 解析只负责给出安全的输入,指出问题是 `validate` 的事。
559
+ */
560
+ declare function resolveComponent(contract: ComponentContract, raw?: ComponentSettings): ResolvedComponent;
561
+
562
+ /**
563
+ * 渲染注册表与三件产物的签名。
564
+ *
565
+ * ```
566
+ * renderHtml(type, settings, instanceId): string // 看得到实例
567
+ * renderCss(type): string // 看不到,类型上就看不到
568
+ * renderJs(type): string // 同上
569
+ * ```
570
+ *
571
+ * **`css` / `js` 不接设置,这是类型签名层面的保证,不是约定**(design D2)。
572
+ * 同一类型的多个实例共用同一份样式与同一份脚本,去重按类型做;而 v2 的形态是
573
+ * 「先按实例渲染、再想办法去重」—— 一旦某个渲染器把颜色写进了 CSS 规则,去重就悄悄
574
+ * 失效,一页六个 tabs 又变回六份 CSS,**且没有任何报错**。签名里没有 settings,
575
+ * 那种写法根本编译不过。
576
+ *
577
+ * 实例差异只有两条通道:标记上的 `data-*` 属性(给属性选择器与脚本读)、
578
+ * `--pt-<comp>-<slot>` 变量(给样式取值)。
579
+ *
580
+ * 渲染是纯函数:进类型与设置,出字符串,**手里没有 Document**。源永远是空壳,
581
+ * 展开只发生在画布注入与成品拼装那一刻。
582
+ */
583
+
584
+ /**
585
+ * 一个实例的结构标记。
586
+ *
587
+ * `settings` 是**原始**设置(实例上 `data-pt-component-settings` 里写着的东西);
588
+ * 契约默认值的合并在这里面做,调用方不必先解析。缺省字段取契约默认值,
589
+ * 类型不符的字段回落默认值(指出问题是 `validate` 的事,不是渲染的)。
590
+ */
591
+ declare function renderHtml(type: string, settings: ComponentSettings, instanceId: string): string;
592
+ /** 一种类型的样式,一份。**不接设置** —— 见文件头。 */
593
+ declare function renderCss(type: string): string;
594
+ /** 一种类型的行为脚本,一份。**不接设置** —— 见文件头。 */
595
+ declare function renderJs(type: string): string;
596
+
597
+ /**
598
+ * 五件内置组件的**字段契约数据**。形状见 `contract.ts`。
599
+ *
600
+ * 逐字段的「留 / 删 / 转 `--pt-*`」判定见本 change 的 `field-audit.md`(84 个 v2 字段
601
+ * 过 design D1 的三条判据:①内容 ②改变结构或行为 ③脚本要读它,其余一律删)。
602
+ * 这里只是那张表的结果,不重复理由 —— 但有两条口径值得在代码旁边留一句,因为
603
+ * 照着 v2 抄会自然违反它们:
604
+ *
605
+ * 1. **文案型字段的默认值一律空串。** v2 的 `'Submit'` / `'Tab'` / `'Question?'` /
606
+ * `'Slide Title'` 不照搬:core 是公开 npm 包,装文案就得装三语。日文页的 AI 漏给
607
+ * `submitText`,线上是个写着 Submit 的英文按钮且不报错;空串渲出空按钮 ——
608
+ * 那是**可见的**错。结构型默认值照给(`buttonUrl: '#'` / `interval: 5000` /
609
+ * `defaultTab: 0`),它们不是用户可见文字。
610
+ * 2. **颜色、字号、圆角、间距、对齐一个都不在这里。** 颜色走实例根元素上的
611
+ * `--pt-<comp>-<slot>`(D1 的唯一例外),刻度类由组件 CSS 直接读页面 `@theme` 的
612
+ * `--radius-*` / `--spacing` / `--text-*`。往这里加一条 `titleColor` 就是把 v4
613
+ * 退回 v2。
614
+ *
615
+ * **字段 id 跨组件同名必须同义**:`buttonText` / `buttonUrl`(carousel 的 slide 与
616
+ * sticky-cta)、`title`(section 或条目的标题)、`label`(form 里字段的标题,与
617
+ * sticky-cta 的按钮文字**有意不同名** —— 同 id 异义是 AI 最容易写混的一种)。
618
+ *
619
+ * 字段 id 集合由 `__tests__/contracts.spec.ts` 逐件锁定 —— 契约是对外的(apps/x 的
620
+ * 面板按它渲染、mastra 按它生成),加一条删一条都要在 review 里被看见。
621
+ */
622
+
623
+ /**
624
+ * 轮播。v2 叫 `swiper`,改名是因为产物里禁止出现 `swiper-slide` 类名
625
+ * (SDK 表单模块用它定校验范围,撞名会让弹窗表单永远校验失败,见 tasks 2.5)。
626
+ *
627
+ * `layout` 的 v2 枚举有第三档 `peek`,渲染器里直接回落 `hero` —— 从未实现,不带过来。
628
+ * `dotColor` → `--pt-carousel-dot-on`(未激活态一并收成 `--pt-carousel-dot-off`),
629
+ * slide 的 `bgColor` → `--pt-carousel-slide-bg`,`dotOffset` 删。
630
+ *
631
+ * ⚠️ `--pt-carousel-slide-bg` 是**实例级一个值、全部 slide 共用**,不是逐 slide 的。
632
+ * 逐 slide 的取值没有第二条通道:slide 元素由渲染器生成,作者没有地方写 class、也没有
633
+ * 地方挂 `--pt-*` 内联样式,唯一入口就是该块的 settings —— 所以「转变量」对块级字段
634
+ * 根本不成立,真正的选项只有「留成块字段」与「删」。按 D1 的三条判据,逐 slide 底色
635
+ * 一条都不满足,删;而它的恢复是**兼容变更**(加字段兼容、删字段不兼容),真有页面
636
+ * 需要时再加,代价近乎零。2.5 写渲染器时:无 image 的 slide 回落到这个实例级变量。
637
+ */
638
+ declare const carouselContract: ComponentContract;
639
+ /**
640
+ * 常见问题。
641
+ *
642
+ * `answer` 是 `richtext`(原样插入),`question` 是 `text`(必须转义)—— 这个区别
643
+ * v2 只存在于渲染器代码里(`esc(question)` 与 `String(answer)`),没进 schema,
644
+ * 于是面板与 AI 都看不见它。它关乎注入安全,属于契约。
645
+ *
646
+ * 18 个组件级字段里留 3 个:`titleAlign` / `titleColor` / `questionColor` /
647
+ * `titleSize` / `questionSize` / `answerSize` / `questionLineHeight` / `answerBg` /
648
+ * `containerBg` / `itemRadius` / `itemGap` / `titleGap` 全删(根 class 与 `@theme`),
649
+ * `answerColor` / `borderColor` / `summaryBg` 转 `--pt-faq-answer-fg` /
650
+ * `--pt-faq-border` / `--pt-faq-summary-bg`。
651
+ */
652
+ declare const faqContract: ComponentContract;
653
+ /**
654
+ * 选项卡。
655
+ *
656
+ * `tabStyle` 用 schema 的 `boxed`,**不是** v2 渲染器里比的 `'box'` —— 那条漂移让
657
+ * boxed 档静默落到 underline 分支(`componentRenderers.ts:142`)。
658
+ *
659
+ * `activeColor` → `--pt-tabs-accent`(激活态由脚本切属性、属性选择器取色,外层 class
660
+ * 够不着,且三档全引用它);v2 渲染器读、而 schema 里根本不存在的 `borderColor` 收成
661
+ * `--pt-tabs-border`,`inactiveColor` 由继承色派生、不立通道。
662
+ * `tabFontSize` / `contentPadding` 删(后者在 v2 里类型就对不上:schema 声明 number、
663
+ * 渲染器当字符串拼进 style,面板填 24 产出无单位的 `padding:24`)。
664
+ */
665
+ declare const tabsContract: ComponentContract;
666
+ /**
667
+ * 表单。v2 叫 `dynamic-form`。41 个字段留 18。
668
+ *
669
+ * 枚举收敛三处,都是在消掉 v2 自身的漂移,不是偏好:
670
+ *
671
+ * - `fieldType` 13 → 11:去掉 `cascade` 与 `postal`(v2 文件头自己写着"尚未支持",
672
+ * 渲染器里 `cascade` 退化成普通 select、`postal` 退化成 text)。随之 `cascadeData` /
673
+ * `cascadeGroup` / `cascadeLevel` / `postalGroup` / `postalRole` 五个字段一并删 ——
674
+ * 契约不该发一个没有消费端的字段,那正是 D3 要堵的失败模式。
675
+ * - `width` 7 档 → `full | half`:v2 的枚举是 `1/4|1/3|1/2|2/3|3/4|full|auto`,而渲染器
676
+ * 只认 `'half'` —— `'half'` **不在枚举里**,所以这个字段今天 100% 失效,面板上是个
677
+ * 选了毫无效果的下拉。
678
+ * - `nextText` / `showConfirm` / `confirmText` 删:渲染器一个都不读。
679
+ *
680
+ * `requiredBadgeText` 是 settings 字段而**不是 core 查表**(tasks 2.6)—— core 不装文案,
681
+ * 所以它的默认值也是空串,尽管 v2 给的是 `'必須'`。
682
+ *
683
+ * 颜色六条转变量:`requiredBadgeColor` → `--pt-form-required-fg`;`submitColor` 与
684
+ * `stepActiveColor` 合并成 `--pt-form-accent`(默认值本就相同,语义同为「主色块背景」);
685
+ * `submitTextColor` → `--pt-form-on-accent`;`inputBorderColor` / `inputBgColor` /
686
+ * `inputTextColor` → `--pt-form-field-border` / `-bg` / `-fg`。
687
+ * `stepInactiveColor` 从 accent 派生、`submitFullWidth` 改由组件 CSS 默认做对
688
+ * (手机满宽、`md` 起自适应)、`inputRadius` / `fieldSpacing` 走 `@theme`。
689
+ */
690
+ declare const formContract: ComponentContract;
691
+ /**
692
+ * 底部吸附条。五件里唯一没有 v2 对应物的一件,但**有一个既有用例且在 golden 里**:
693
+ * `__fixtures__/page/02-vivaia-campaign-lp.html` 的 `k1` 节点写着
694
+ * `{label, href, showAfter, hideOnPc, align}`,而那份 fixture 过 page-fixtures 的
695
+ * 「占位除外零 issue」断言 —— 所以 4.2 落地后这里认不认这几个字段,直接决定那条既有
696
+ * 测试红不红。对齐由 tasks 1.5 做(改 fixture + 重生成 canonical golden),是已知代价。
697
+ *
698
+ * 三处与 fixture 不同,都有理由:
699
+ * - `label` → `buttonText`、`href` → `buttonUrl`:与 carousel 的 slide 块对齐,且避开
700
+ * `label` 在 form 的 field 块里的另一层含义(字段标题)。
701
+ * - `hideOnPc` → `showOn`:正向表述,没有双重否定。fixture 的 `hideOnPc: false`
702
+ * 对应 `showOn: 'all'`。
703
+ *
704
+ * **不是字段**:底部留白与 safe-area 是组件 CSS 的固有行为(`env(safe-area-inset-bottom)`
705
+ * + 给页面留出等高空白),是 tasks 2.7 的验收项,不是旋钮。
706
+ */
707
+ declare const stickyCtaContract: ComponentContract;
708
+ /**
709
+ * 全部内置组件的契约,按类型 id 索引。
710
+ *
711
+ * `validate` 与渲染器注册表都读它;**契约里没有的类型就是未知类型**,不存在「渲染器
712
+ * 有、契约没有」的中间态 —— 那正是 v2 烧过的「改了没反应、无报错」。
713
+ */
714
+ declare const COMPONENT_CONTRACTS: Readonly<Record<string, ComponentContract>>;
715
+ /** 内置组件的类型 id。顺序即上面的声明顺序。 */
716
+ declare const COMPONENT_TYPES: readonly string[];
717
+
718
+ /**
719
+ * 内置组件的公开面。以 `components` 命名空间整体导出(同 `compile` / `guards` 的惯例),
720
+ * 这样加一件组件、加一个契约字段都不必动顶层导出名的锁定测试 —— 那条锁该盯的是
721
+ * 「公开面多了一整块能力」,不是「契约里多了一个字段」。
722
+ */
723
+
724
+ declare const components_BLOCKS_KEY: typeof BLOCKS_KEY;
725
+ declare const components_COMPONENT_CONTRACTS: typeof COMPONENT_CONTRACTS;
726
+ declare const components_COMPONENT_TYPES: typeof COMPONENT_TYPES;
727
+ type components_ComponentBlockSpec = ComponentBlockSpec;
728
+ type components_ComponentBlockValue = ComponentBlockValue;
729
+ type components_ComponentContract = ComponentContract;
730
+ type components_ComponentSettingSpec = ComponentSettingSpec;
731
+ type components_ComponentSettings = ComponentSettings;
732
+ type components_ResolvedBlock = ResolvedBlock;
733
+ type components_ResolvedComponent = ResolvedComponent;
734
+ type components_ResolvedSettings = ResolvedSettings;
735
+ declare const components_SETTING_TYPES: typeof SETTING_TYPES;
736
+ type components_SettingType = SettingType;
737
+ type components_SettingValue = SettingValue;
738
+ declare const components_carouselContract: typeof carouselContract;
739
+ declare const components_faqContract: typeof faqContract;
740
+ declare const components_formContract: typeof formContract;
741
+ declare const components_renderCss: typeof renderCss;
742
+ declare const components_renderHtml: typeof renderHtml;
743
+ declare const components_renderJs: typeof renderJs;
744
+ declare const components_resolveComponent: typeof resolveComponent;
745
+ declare const components_stickyCtaContract: typeof stickyCtaContract;
746
+ declare const components_tabsContract: typeof tabsContract;
747
+ declare namespace components {
748
+ export { components_BLOCKS_KEY as BLOCKS_KEY, components_COMPONENT_CONTRACTS as COMPONENT_CONTRACTS, components_COMPONENT_TYPES as COMPONENT_TYPES, type components_ComponentBlockSpec as ComponentBlockSpec, type components_ComponentBlockValue as ComponentBlockValue, type components_ComponentContract as ComponentContract, type components_ComponentSettingSpec as ComponentSettingSpec, type components_ComponentSettings as ComponentSettings, type components_ResolvedBlock as ResolvedBlock, type components_ResolvedComponent as ResolvedComponent, type components_ResolvedSettings as ResolvedSettings, components_SETTING_TYPES as SETTING_TYPES, type components_SettingType as SettingType, type components_SettingValue as SettingValue, components_carouselContract as carouselContract, components_faqContract as faqContract, components_formContract as formContract, components_renderCss as renderCss, components_renderHtml as renderHtml, components_renderJs as renderJs, components_resolveComponent as resolveComponent, components_stickyCtaContract as stickyCtaContract, components_tabsContract as tabsContract };
749
+ }
750
+
403
751
  /**
404
752
  * 收集文档里用到的全部 class。
405
753
  *
@@ -585,8 +933,8 @@ declare function checkVariants(className: string): VariantCheck;
585
933
  * 护栏:export golden 断言「输出变了而 COMPILER_VERSION 没变就红」。
586
934
  * 递增它是一次显式动作,不要顺手跟着包版本一起改。
587
935
  */
588
- declare const VERSION = "1.0.0-alpha.3";
589
- declare const COMPILER_VERSION = "1";
936
+ declare const VERSION = "1.0.0-alpha.5";
937
+ declare const COMPILER_VERSION = "2";
590
938
 
591
939
  /**
592
940
  * core 的入口。宿主注入 DOM 之后拿到一组行为。
@@ -606,6 +954,7 @@ declare function createCore(options: CoreOptions): {
606
954
  apply: typeof apply;
607
955
  guards: typeof guards;
608
956
  compile: typeof compile;
957
+ components: typeof components;
609
958
  collectClasses: typeof collectClasses;
610
959
  rules: {
611
960
  readonly deviceVariants: readonly ["md", "max-md"];
@@ -665,4 +1014,4 @@ declare function createCore(options: CoreOptions): {
665
1014
  };
666
1015
  type Core = ReturnType<typeof createCore>;
667
1016
 
668
- export { type ApplyResult, COMPILER_VERSION, type Core, type CoreOptions, type DOMParserCtor, type DOMParserLike, type DeleteOp, type DeviceVariant, EMPTY_THEME, ERROR_CODES, type EditOp, type EditorError, type ErrorCode, type InsertOp, type Issue, type LayerName, type LeafType, MAX_CONTAINER_DEPTH, type MoveOp, type NormalizeOptions, type OpaqueMode, PUBLISH_BLOCKING_RULES, type PropertySlot, type ReplaceOp, type Rules, type SetOp, type StateVariant, type StructureRejection, type Theme, type ThemeTokens, type Transaction, VERSION, type ValidateOptions, type ValidateResult$1 as ValidateResult, apply, canContain, checkVariants, collectClasses, compile, containerDepth, createCore, deleteTarget, editorError, fragmentElementRoots, guards, isErrorCode, isNativePaletteClass, isPublishBlocking, isWrapLayer, layerOf, normalize, normalizeClassValue, parseFragment, rules, serialize, themeFromSource, themeOf, themeTokens, unwrap, validate };
1017
+ export { type ApplyResult, COMPILER_VERSION, type Core, type CoreOptions, type DOMParserCtor, type DOMParserLike, type DeleteOp, type DeviceVariant, EMPTY_THEME, ERROR_CODES, type EditOp, type EditorError, type ErrorCode, type InsertOp, type Issue, type LayerName, type LeafType, MAX_CONTAINER_DEPTH, type MoveOp, type NormalizeOptions, type OpaqueMode, PUBLISH_BLOCKING_RULES, type PropertySlot, type ReplaceOp, type Rules, type SetOp, type StateVariant, type StructureRejection, type Theme, type ThemeTokens, type Transaction, VERSION, type ValidateOptions, type ValidateResult$1 as ValidateResult, apply, canContain, checkVariants, collectClasses, compile, components, containerDepth, createCore, deleteTarget, editorError, fragmentElementRoots, guards, isErrorCode, isNativePaletteClass, isPublishBlocking, isWrapLayer, layerOf, normalize, normalizeClassValue, parseFragment, rules, serialize, themeFromSource, themeOf, themeTokens, unwrap, validate };