@wp-typia/project-tools 0.23.1 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/runtime/built-in-block-non-ts-basic-artifacts.d.ts +9 -0
  2. package/dist/runtime/built-in-block-non-ts-basic-artifacts.js +84 -0
  3. package/dist/runtime/built-in-block-non-ts-compound-artifacts.d.ts +9 -0
  4. package/dist/runtime/built-in-block-non-ts-compound-artifacts.js +36 -0
  5. package/dist/runtime/built-in-block-non-ts-compound-templates.d.ts +23 -0
  6. package/dist/runtime/built-in-block-non-ts-compound-templates.js +453 -0
  7. package/dist/runtime/built-in-block-non-ts-family-artifacts.d.ts +8 -26
  8. package/dist/runtime/built-in-block-non-ts-family-artifacts.js +8 -1034
  9. package/dist/runtime/built-in-block-non-ts-interactivity-artifacts.d.ts +9 -0
  10. package/dist/runtime/built-in-block-non-ts-interactivity-artifacts.js +83 -0
  11. package/dist/runtime/built-in-block-non-ts-persistence-artifacts.d.ts +9 -0
  12. package/dist/runtime/built-in-block-non-ts-persistence-artifacts.js +33 -0
  13. package/dist/runtime/built-in-block-non-ts-persistence-templates.d.ts +23 -0
  14. package/dist/runtime/built-in-block-non-ts-persistence-templates.js +395 -0
  15. package/dist/runtime/cli-add-collision.js +8 -0
  16. package/dist/runtime/cli-add-help.js +10 -7
  17. package/dist/runtime/cli-add-kind-ids.d.ts +1 -1
  18. package/dist/runtime/cli-add-kind-ids.js +1 -0
  19. package/dist/runtime/cli-add-types.d.ts +28 -1
  20. package/dist/runtime/cli-add-types.js +2 -0
  21. package/dist/runtime/cli-add-workspace-ability-anchors.d.ts +24 -0
  22. package/dist/runtime/cli-add-workspace-ability-anchors.js +294 -0
  23. package/dist/runtime/cli-add-workspace-ability-registry.d.ts +10 -0
  24. package/dist/runtime/cli-add-workspace-ability-registry.js +51 -0
  25. package/dist/runtime/cli-add-workspace-ability-scaffold.d.ts +1 -1
  26. package/dist/runtime/cli-add-workspace-ability-scaffold.js +5 -311
  27. package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +1 -1
  28. package/dist/runtime/cli-add-workspace-ai-anchors.d.ts +4 -4
  29. package/dist/runtime/cli-add-workspace-ai-anchors.js +4 -232
  30. package/dist/runtime/cli-add-workspace-ai-scaffold.js +4 -2
  31. package/dist/runtime/cli-add-workspace-ai-source-emitters.d.ts +1 -4
  32. package/dist/runtime/cli-add-workspace-ai-source-emitters.js +1 -145
  33. package/dist/runtime/cli-add-workspace-ai-sync-rest-anchors.d.ts +5 -0
  34. package/dist/runtime/cli-add-workspace-ai-sync-rest-anchors.js +236 -0
  35. package/dist/runtime/cli-add-workspace-ai-sync-script-source.d.ts +4 -0
  36. package/dist/runtime/cli-add-workspace-ai-sync-script-source.js +145 -0
  37. package/dist/runtime/cli-add-workspace-assets.d.ts +6 -63
  38. package/dist/runtime/cli-add-workspace-assets.js +6 -950
  39. package/dist/runtime/cli-add-workspace-binding-source-anchors.d.ts +23 -0
  40. package/dist/runtime/cli-add-workspace-binding-source-anchors.js +112 -0
  41. package/dist/runtime/cli-add-workspace-binding-source-source-emitters.d.ts +33 -0
  42. package/dist/runtime/cli-add-workspace-binding-source-source-emitters.js +436 -0
  43. package/dist/runtime/cli-add-workspace-binding-source-types.d.ts +20 -0
  44. package/dist/runtime/cli-add-workspace-binding-source-types.js +1 -0
  45. package/dist/runtime/cli-add-workspace-binding-source.d.ts +40 -0
  46. package/dist/runtime/cli-add-workspace-binding-source.js +275 -0
  47. package/dist/runtime/cli-add-workspace-block-style.d.ts +22 -0
  48. package/dist/runtime/cli-add-workspace-block-style.js +148 -0
  49. package/dist/runtime/cli-add-workspace-block-transform.d.ts +32 -0
  50. package/dist/runtime/cli-add-workspace-block-transform.js +197 -0
  51. package/dist/runtime/cli-add-workspace-contract.js +1 -1
  52. package/dist/runtime/cli-add-workspace-core-variation.d.ts +20 -0
  53. package/dist/runtime/cli-add-workspace-core-variation.js +322 -0
  54. package/dist/runtime/cli-add-workspace-editor-plugin-anchors.d.ts +37 -0
  55. package/dist/runtime/cli-add-workspace-editor-plugin-anchors.js +206 -0
  56. package/dist/runtime/cli-add-workspace-editor-plugin-source-emitters.d.ts +47 -0
  57. package/dist/runtime/cli-add-workspace-editor-plugin-source-emitters.js +219 -0
  58. package/dist/runtime/cli-add-workspace-editor-plugin.d.ts +22 -0
  59. package/dist/runtime/cli-add-workspace-editor-plugin.js +78 -0
  60. package/dist/runtime/cli-add-workspace-hooked-block.d.ts +23 -0
  61. package/dist/runtime/cli-add-workspace-hooked-block.js +57 -0
  62. package/dist/runtime/cli-add-workspace-integration-env-files.d.ts +33 -0
  63. package/dist/runtime/cli-add-workspace-integration-env-files.js +65 -0
  64. package/dist/runtime/cli-add-workspace-integration-env-package-json.d.ts +38 -0
  65. package/dist/runtime/cli-add-workspace-integration-env-package-json.js +122 -0
  66. package/dist/runtime/cli-add-workspace-integration-env-source-emitters.d.ts +44 -0
  67. package/dist/runtime/cli-add-workspace-integration-env-source-emitters.js +262 -0
  68. package/dist/runtime/cli-add-workspace-integration-env.js +5 -345
  69. package/dist/runtime/cli-add-workspace-pattern-anchors.d.ts +10 -0
  70. package/dist/runtime/cli-add-workspace-pattern-anchors.js +95 -0
  71. package/dist/runtime/cli-add-workspace-pattern-options.d.ts +20 -0
  72. package/dist/runtime/cli-add-workspace-pattern-options.js +113 -0
  73. package/dist/runtime/cli-add-workspace-pattern-source-emitters.d.ts +20 -0
  74. package/dist/runtime/cli-add-workspace-pattern-source-emitters.js +57 -0
  75. package/dist/runtime/cli-add-workspace-pattern.d.ts +42 -0
  76. package/dist/runtime/cli-add-workspace-pattern.js +99 -0
  77. package/dist/runtime/cli-add-workspace-post-meta.js +1 -1
  78. package/dist/runtime/cli-add-workspace-registration-hooks.d.ts +50 -0
  79. package/dist/runtime/cli-add-workspace-registration-hooks.js +162 -0
  80. package/dist/runtime/cli-add-workspace-rest-anchors.d.ts +6 -9
  81. package/dist/runtime/cli-add-workspace-rest-anchors.js +6 -466
  82. package/dist/runtime/cli-add-workspace-rest-bootstrap-anchors.d.ts +17 -0
  83. package/dist/runtime/cli-add-workspace-rest-bootstrap-anchors.js +108 -0
  84. package/dist/runtime/cli-add-workspace-rest-contract-sync-anchors.d.ts +9 -0
  85. package/dist/runtime/cli-add-workspace-rest-contract-sync-anchors.js +142 -0
  86. package/dist/runtime/cli-add-workspace-rest-generated-source-emitters.d.ts +51 -0
  87. package/dist/runtime/cli-add-workspace-rest-generated-source-emitters.js +415 -0
  88. package/dist/runtime/cli-add-workspace-rest-generated.js +5 -3
  89. package/dist/runtime/cli-add-workspace-rest-manual-source-emitters.d.ts +80 -0
  90. package/dist/runtime/cli-add-workspace-rest-manual-source-emitters.js +238 -0
  91. package/dist/runtime/cli-add-workspace-rest-manual.js +3 -16
  92. package/dist/runtime/cli-add-workspace-rest-php-templates.d.ts +1 -7
  93. package/dist/runtime/cli-add-workspace-rest-php-templates.js +3 -322
  94. package/dist/runtime/cli-add-workspace-rest-resource-php-routing-template.d.ts +33 -0
  95. package/dist/runtime/cli-add-workspace-rest-resource-php-routing-template.js +145 -0
  96. package/dist/runtime/cli-add-workspace-rest-resource-sync-anchors.d.ts +9 -0
  97. package/dist/runtime/cli-add-workspace-rest-resource-sync-anchors.js +162 -0
  98. package/dist/runtime/cli-add-workspace-rest-schema-helper-php-template.d.ts +7 -0
  99. package/dist/runtime/cli-add-workspace-rest-schema-helper-php-template.js +193 -0
  100. package/dist/runtime/cli-add-workspace-rest-source-emitters.d.ts +5 -99
  101. package/dist/runtime/cli-add-workspace-rest-source-emitters.js +5 -663
  102. package/dist/runtime/cli-add-workspace-rest-source-utils.d.ts +17 -0
  103. package/dist/runtime/cli-add-workspace-rest-source-utils.js +50 -0
  104. package/dist/runtime/cli-add-workspace-rest-sync-script-shared.d.ts +56 -0
  105. package/dist/runtime/cli-add-workspace-rest-sync-script-shared.js +122 -0
  106. package/dist/runtime/cli-add-workspace-rest-types.d.ts +3 -3
  107. package/dist/runtime/cli-add-workspace-variation.d.ts +22 -0
  108. package/dist/runtime/cli-add-workspace-variation.js +162 -0
  109. package/dist/runtime/cli-add-workspace.d.ts +42 -107
  110. package/dist/runtime/cli-add-workspace.js +42 -674
  111. package/dist/runtime/cli-add.d.ts +3 -3
  112. package/dist/runtime/cli-add.js +2 -2
  113. package/dist/runtime/cli-core.d.ts +2 -1
  114. package/dist/runtime/cli-core.js +1 -1
  115. package/dist/runtime/cli-doctor-workspace-bindings.js +59 -0
  116. package/dist/runtime/cli-doctor-workspace-block-addons.js +33 -5
  117. package/dist/runtime/cli-doctor.d.ts +2 -0
  118. package/dist/runtime/cli-doctor.js +13 -2
  119. package/dist/runtime/cli-help.js +6 -4
  120. package/dist/runtime/index.d.ts +5 -2
  121. package/dist/runtime/index.js +4 -2
  122. package/dist/runtime/local-dev-presets.js +2 -1
  123. package/dist/runtime/package-versions.d.ts +1 -0
  124. package/dist/runtime/package-versions.js +10 -2
  125. package/dist/runtime/pattern-catalog.d.ts +122 -0
  126. package/dist/runtime/pattern-catalog.js +471 -0
  127. package/dist/runtime/post-meta-binding-fields.d.ts +46 -0
  128. package/dist/runtime/post-meta-binding-fields.js +135 -0
  129. package/dist/runtime/typia-llm-json-schema.d.ts +24 -0
  130. package/dist/runtime/typia-llm-json-schema.js +33 -0
  131. package/dist/runtime/typia-llm-openapi-constraints.d.ts +20 -0
  132. package/dist/runtime/typia-llm-openapi-constraints.js +254 -0
  133. package/dist/runtime/typia-llm-projection.d.ts +25 -0
  134. package/dist/runtime/typia-llm-projection.js +58 -0
  135. package/dist/runtime/typia-llm-render.d.ts +21 -0
  136. package/dist/runtime/typia-llm-render.js +252 -0
  137. package/dist/runtime/typia-llm-sync.d.ts +10 -0
  138. package/dist/runtime/typia-llm-sync.js +63 -0
  139. package/dist/runtime/typia-llm-types.d.ts +197 -0
  140. package/dist/runtime/typia-llm-types.js +1 -0
  141. package/dist/runtime/typia-llm.d.ts +9 -255
  142. package/dist/runtime/typia-llm.js +5 -634
  143. package/dist/runtime/workspace-inventory-mutations.js +13 -0
  144. package/dist/runtime/workspace-inventory-section-descriptors.js +9 -1
  145. package/dist/runtime/workspace-inventory-templates.d.ts +2 -2
  146. package/dist/runtime/workspace-inventory-templates.js +9 -1
  147. package/dist/runtime/workspace-inventory-types.d.ts +9 -1
  148. package/package.json +8 -3
  149. package/templates/_shared/compound/core/scripts/block-config.ts.mustache +22 -0
  150. package/templates/_shared/compound/core/scripts/sync-types-to-block-json.ts.mustache +103 -2
  151. package/templates/_shared/compound/core/src/inner-blocks-templates.ts.mustache +13 -0
  152. package/templates/_shared/compound/persistence/scripts/block-config.ts.mustache +22 -1
@@ -0,0 +1,9 @@
1
+ import type { BuiltInCodeArtifact } from "./built-in-block-code-artifacts.js";
2
+ import type { ScaffoldTemplateVariables } from "./scaffold.js";
3
+ /**
4
+ * Builds the basic family non-TypeScript scaffold artifacts.
5
+ *
6
+ * @param variables Scaffold template variables used to render the artifact set.
7
+ * @returns The SCSS and PHP artifacts owned by the basic template family.
8
+ */
9
+ export declare function buildBasicArtifacts(variables: ScaffoldTemplateVariables): BuiltInCodeArtifact[];
@@ -0,0 +1,84 @@
1
+ import { renderArtifact } from "./built-in-block-non-ts-render-utils.js";
2
+ const BASIC_STYLE_TEMPLATE = `/**
3
+ * {{title}} Block Styles
4
+ */
5
+
6
+ .{{cssClassName}} {
7
+ padding: 20px;
8
+ border: 1px solid #ddd;
9
+ border-radius: 4px;
10
+ background-color: #fff;
11
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
12
+
13
+ &.is-hidden {
14
+ display: none;
15
+ }
16
+
17
+ &__content {
18
+ font-size: 16px;
19
+ line-height: 1.6;
20
+ color: #333;
21
+
22
+ // Alignment styles
23
+ &[data-align="center"] {
24
+ text-align: center;
25
+ }
26
+
27
+ &[data-align="right"] {
28
+ text-align: right;
29
+ }
30
+
31
+ &[data-align="justify"] {
32
+ text-align: justify;
33
+ }
34
+ }
35
+
36
+ // Hover state
37
+ &:hover {
38
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
39
+ transition: box-shadow 0.2s ease;
40
+ }
41
+ }
42
+ `;
43
+ const BASIC_EDITOR_STYLE_TEMPLATE = `/**
44
+ * {{title}} Block Editor Styles
45
+ */
46
+
47
+ .{{cssClassName}} {
48
+ outline: 1px dashed #ddd;
49
+ outline-offset: -1px;
50
+ }
51
+ `;
52
+ const BASIC_RENDER_TEMPLATE = `<?php
53
+ /**
54
+ * Optional server render placeholder for {{title}}.
55
+ *
56
+ * The basic scaffold stays static by default. Keep \`src/save.tsx\` as the
57
+ * canonical frontend output unless you intentionally convert this block into a
58
+ * dynamic render path and add \`render\` to \`block.json\`.
59
+ *
60
+ * @package {{slug}}
61
+ */
62
+
63
+ if ( ! defined( 'ABSPATH' ) ) {
64
+ exit;
65
+ }
66
+
67
+ ?>
68
+ <div class="{{cssClassName}}__server-placeholder" hidden>
69
+ <?php esc_html_e( 'Server render placeholder.', '{{textDomain}}' ); ?>
70
+ </div>
71
+ `;
72
+ /**
73
+ * Builds the basic family non-TypeScript scaffold artifacts.
74
+ *
75
+ * @param variables Scaffold template variables used to render the artifact set.
76
+ * @returns The SCSS and PHP artifacts owned by the basic template family.
77
+ */
78
+ export function buildBasicArtifacts(variables) {
79
+ return [
80
+ renderArtifact("src/editor.scss", BASIC_EDITOR_STYLE_TEMPLATE, variables),
81
+ renderArtifact("src/style.scss", BASIC_STYLE_TEMPLATE, variables),
82
+ renderArtifact("src/render.php", BASIC_RENDER_TEMPLATE, variables),
83
+ ];
84
+ }
@@ -0,0 +1,9 @@
1
+ import type { BuiltInCodeArtifact } from "./built-in-block-code-artifacts.js";
2
+ import type { ScaffoldTemplateVariables } from "./scaffold.js";
3
+ /**
4
+ * Builds the compound family non-TypeScript scaffold artifacts.
5
+ *
6
+ * @param variables Scaffold template variables used to render the artifact set.
7
+ * @returns The compound SCSS and PHP artifacts, including persistence variants when enabled.
8
+ */
9
+ export declare function buildCompoundArtifacts(variables: ScaffoldTemplateVariables): BuiltInCodeArtifact[];
@@ -0,0 +1,36 @@
1
+ import { buildAlternateRenderEntryArtifact, renderArtifact, toPhpSingleQuotedString, } from "./built-in-block-non-ts-render-utils.js";
2
+ import { COMPOUND_PERSISTENCE_RENDER_TARGETS_TEMPLATE, COMPOUND_PERSISTENCE_RENDER_TEMPLATE, COMPOUND_STYLE_TEMPLATE, } from "./built-in-block-non-ts-compound-templates.js";
3
+ import { getScaffoldAlternateRenderTargets, isCompoundPersistenceEnabled, } from "./scaffold-template-variable-groups.js";
4
+ /**
5
+ * Builds the compound family non-TypeScript scaffold artifacts.
6
+ *
7
+ * @param variables Scaffold template variables used to render the artifact set.
8
+ * @returns The compound SCSS and PHP artifacts, including persistence variants when enabled.
9
+ */
10
+ export function buildCompoundArtifacts(variables) {
11
+ const alternateRenderTargets = getScaffoldAlternateRenderTargets(variables);
12
+ const artifacts = [
13
+ renderArtifact(`src/blocks/${variables.slugKebabCase}/style.scss`, COMPOUND_STYLE_TEMPLATE, variables),
14
+ ];
15
+ if (isCompoundPersistenceEnabled(variables)) {
16
+ const renderView = {
17
+ ...variables,
18
+ titlePhpLiteral: toPhpSingleQuotedString(variables.title),
19
+ };
20
+ if (alternateRenderTargets.enabled) {
21
+ artifacts.push(renderArtifact(`src/blocks/${variables.slugKebabCase}/render-targets.php`, COMPOUND_PERSISTENCE_RENDER_TARGETS_TEMPLATE, renderView), buildAlternateRenderEntryArtifact(`src/blocks/${variables.slugKebabCase}/render.php`, "web", variables));
22
+ if (alternateRenderTargets.hasEmail) {
23
+ artifacts.push(buildAlternateRenderEntryArtifact(`src/blocks/${variables.slugKebabCase}/render-email.php`, "email", variables));
24
+ }
25
+ if (alternateRenderTargets.hasMjml) {
26
+ artifacts.push(buildAlternateRenderEntryArtifact(`src/blocks/${variables.slugKebabCase}/render-mjml.php`, "mjml", variables));
27
+ }
28
+ if (alternateRenderTargets.hasPlainText) {
29
+ artifacts.push(buildAlternateRenderEntryArtifact(`src/blocks/${variables.slugKebabCase}/render-text.php`, "plain-text", variables));
30
+ }
31
+ return artifacts;
32
+ }
33
+ artifacts.push(renderArtifact(`src/blocks/${variables.slugKebabCase}/render.php`, COMPOUND_PERSISTENCE_RENDER_TEMPLATE, renderView));
34
+ }
35
+ return artifacts;
36
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * PHP helper template for compound parent alternate render targets.
3
+ *
4
+ * @remarks Consumed by `buildCompoundArtifacts()` for email, MJML,
5
+ * plain-text, and web render entries with block identity, namespace, text
6
+ * domain, storage, policy, CSS class, and title placeholders.
7
+ */
8
+ export declare const COMPOUND_PERSISTENCE_RENDER_TARGETS_TEMPLATE = "<?php\n/**\n * Alternate render target helpers for the {{title}} compound parent block.\n *\n * @package {{pascalCase}}\n */\n\nif ( ! defined( 'ABSPATH' ) ) {\n\texit;\n}\n\nif ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_build_render_context' ) ) {\n\tfunction {{phpPrefix}}_{{slugSnakeCase}}_build_render_context( $attributes, $content, $block ) {\n\t\t$validator_path = __DIR__ . '/typia-validator.php';\n\t\tif ( ! file_exists( $validator_path ) ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$validator = require $validator_path;\n\t\tif ( ! is_object( $validator ) || ! method_exists( $validator, 'apply_defaults' ) || ! method_exists( $validator, 'validate' ) ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$normalized = $validator->apply_defaults( is_array( $attributes ) ? $attributes : array() );\n\t\t$validation = $validator->validate( $normalized );\n\t\t$resource_key = isset( $normalized['resourceKey'] ) ? (string) $normalized['resourceKey'] : '';\n\t\t$heading = isset( $normalized['heading'] ) ? (string) $normalized['heading'] : {{titlePhpLiteral}};\n\t\t$intro = isset( $normalized['intro'] ) ? (string) $normalized['intro'] : '';\n\t\t$button_label = isset( $normalized['buttonLabel'] ) ? (string) $normalized['buttonLabel'] : 'Persist Count';\n\t\t$show_count = ! empty( $normalized['showCount'] );\n\t\t$show_dividers = ! empty( $normalized['showDividers'] );\n\t\t$post_id = is_object( $block ) && isset( $block->context['postId'] )\n\t\t\t? (int) $block->context['postId']\n\t\t\t: (int) get_queried_object_id();\n\t\t$storage_mode = '{{dataStorageMode}}';\n\t\t$persistence_policy = '{{persistencePolicy}}';\n\n\t\t$notice_message = 'authenticated' === $persistence_policy\n\t\t\t? __( 'Sign in to persist this counter.', '{{textDomain}}' )\n\t\t\t: __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );\n\n\t\tif ( empty( $validation['valid'] ) || '' === $resource_key ) {\n\t\t\treturn null;\n\t\t}\n\n\t\t{{phpPrefix}}_record_rendered_block_instance(\n\t\t\t(int) $post_id,\n\t\t\t'{{namespace}}/{{slugKebabCase}}',\n\t\t\t$resource_key\n\t\t);\n\n\t\t$web_context = array(\n\t\t\t'bootstrapReady' => false,\n\t\t\t'buttonLabel' => $button_label,\n\t\t\t'canWrite' => false,\n\t\t\t'client' => array(\n\t\t\t\t'writeExpiry' => 0,\n\t\t\t\t'writeNonce' => '',\n\t\t\t\t'writeToken' => '',\n\t\t\t),\n\t\t\t'count' => 0,\n\t\t\t'error' => '',\n\t\t\t'isBootstrapping' => false,\n\t\t\t'isLoading' => false,\n\t\t\t'isSaving' => false,\n\t\t\t'persistencePolicy' => $persistence_policy,\n\t\t\t'postId' => (int) $post_id,\n\t\t\t'resourceKey' => $resource_key,\n\t\t\t'showCount' => $show_count,\n\t\t\t'storage' => $storage_mode,\n\t\t);\n\n\t\t$allowed_inner_html = wp_kses_allowed_html( 'post' );\n\n\t\tforeach ( $allowed_inner_html as &$allowed_attributes ) {\n\t\t\tif ( ! is_array( $allowed_attributes ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$allowed_attributes['data-wp-bind--disabled'] = true;\n\t\t\t$allowed_attributes['data-wp-bind--hidden'] = true;\n\t\t\t$allowed_attributes['data-wp-bind--value'] = true;\n\t\t\t$allowed_attributes['data-wp-class'] = true;\n\t\t\t$allowed_attributes['data-wp-class--active'] = true;\n\t\t\t$allowed_attributes['data-wp-context'] = true;\n\t\t\t$allowed_attributes['data-wp-init'] = true;\n\t\t\t$allowed_attributes['data-wp-interactive'] = true;\n\t\t\t$allowed_attributes['data-wp-on--click'] = true;\n\t\t\t$allowed_attributes['data-wp-on--mouseenter'] = true;\n\t\t\t$allowed_attributes['data-wp-on--mouseleave'] = true;\n\t\t\t$allowed_attributes['data-wp-run--mounted'] = true;\n\t\t\t$allowed_attributes['data-wp-style--width'] = true;\n\t\t\t$allowed_attributes['data-wp-text'] = true;\n\t\t}\n\t\tunset( $allowed_attributes );\n\n\t\treturn array(\n\t\t\t'buttonLabel' => $button_label,\n\t\t\t'frontendClassName' => '{{cssClassName}}',\n\t\t\t'heading' => $heading,\n\t\t\t'intro' => $intro,\n\t\t\t'isVisible' => true,\n\t\t\t'normalized' => $normalized,\n\t\t\t'noticeMessage' => $notice_message,\n\t\t\t'postId' => (int) $post_id,\n\t\t\t'resourceKey' => $resource_key,\n\t\t\t'sanitizedContent' => wp_kses( $content, $allowed_inner_html ),\n\t\t\t'showCount' => $show_count,\n\t\t\t'showDividers' => $show_dividers,\n\t\t\t'title' => {{titleJson}},\n\t\t\t'webContext' => $web_context,\n\t\t);\n\t}\n}\n\nif ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_render_target' ) ) {\n\tfunction {{phpPrefix}}_{{slugSnakeCase}}_render_target( string $target, $attributes, $content = '', $block = null ): string {\n\t\t$context = {{phpPrefix}}_{{slugSnakeCase}}_build_render_context( $attributes, $content, $block );\n\t\tif ( null === $context ) {\n\t\t\treturn '';\n\t\t}\n\n\t\t$target_context = apply_filters(\n\t\t\t'{{phpPrefix}}_{{slugSnakeCase}}_render_target_context',\n\t\t\t$context,\n\t\t\t$target,\n\t\t\t$attributes,\n\t\t\t$block\n\t\t);\n\n\t\t$markup = '';\n\t\tswitch ( $target ) {\n\t\t\tcase 'email':\n\t\t\t\t$parts = array(\n\t\t\t\t\t'<div class=\"' . esc_attr( $target_context['frontendClassName'] ) . '-email\">',\n\t\t\t\t\t'<h3>' . esc_html( $target_context['heading'] ) . '</h3>',\n\t\t\t\t);\n\t\t\t\tif ( '' !== $target_context['intro'] ) {\n\t\t\t\t\t$parts[] = '<p>' . esc_html( $target_context['intro'] ) . '</p>';\n\t\t\t\t}\n\t\t\t\tif ( ! empty( $target_context['showCount'] ) ) {\n\t\t\t\t\t$parts[] = '<p><strong>' . esc_html__( 'Count', '{{textDomain}}' ) . ':</strong> ' . esc_html( (string) $target_context['webContext']['count'] ) . '</p>';\n\t\t\t\t}\n\t\t\t\t$parts[] = wp_kses_post( $target_context['sanitizedContent'] );\n\t\t\t\t$parts[] = '<p>' . esc_html( $target_context['noticeMessage'] ) . '</p>';\n\t\t\t\t$parts[] = '</div>';\n\t\t\t\t$markup = implode( '', $parts );\n\t\t\t\tbreak;\n\t\t\tcase 'mjml':\n\t\t\t\t$markup = '<mjml><mj-body><mj-section><mj-column><mj-text font-weight=\"700\">' . esc_html( $target_context['heading'] ) . '</mj-text>';\n\t\t\t\tif ( '' !== $target_context['intro'] ) {\n\t\t\t\t\t$markup .= '<mj-text>' . esc_html( $target_context['intro'] ) . '</mj-text>';\n\t\t\t\t}\n\t\t\t\tif ( ! empty( $target_context['showCount'] ) ) {\n\t\t\t\t\t$markup .= '<mj-text>' . esc_html__( 'Count', '{{textDomain}}' ) . ': ' . esc_html( (string) $target_context['webContext']['count'] ) . '</mj-text>';\n\t\t\t\t}\n\t\t\t\t$markup .= '<mj-text>' . esc_html( wp_strip_all_tags( $target_context['sanitizedContent'] ) ) . '</mj-text>';\n\t\t\t\t$markup .= '<mj-text>' . esc_html( $target_context['noticeMessage'] ) . '</mj-text></mj-column></mj-section></mj-body></mjml>';\n\t\t\t\tbreak;\n\t\t\tcase 'plain-text':\n\t\t\t\t$markup = implode(\n\t\t\t\t\t\"\\n\",\n\t\t\t\t\tarray_filter(\n\t\t\t\t\t\tarray(\n\t\t\t\t\t\t\t$target_context['heading'],\n\t\t\t\t\t\t\t$target_context['intro'],\n\t\t\t\t\t\t\twp_strip_all_tags( $target_context['sanitizedContent'] ),\n\t\t\t\t\t\t\t! empty( $target_context['showCount'] )\n\t\t\t\t\t\t\t\t? sprintf( '%s: %s', __( 'Count', '{{textDomain}}' ), (string) $target_context['webContext']['count'] )\n\t\t\t\t\t\t\t\t: null,\n\t\t\t\t\t\t\t$target_context['noticeMessage'],\n\t\t\t\t\t\t),\n\t\t\t\t\t\tstatic fn( $value ) => is_string( $value ) && '' !== $value\n\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase 'web':\n\t\t\tdefault:\n\t\t\t\t$wrapper_attributes = get_block_wrapper_attributes(\n\t\t\t\t\tarray(\n\t\t\t\t\t\t'class' => '{{cssClassName}}',\n\t\t\t\t\t\t'data-show-dividers' => $target_context['showDividers'] ? 'true' : 'false',\n\t\t\t\t\t\t'data-wp-context' => wp_json_encode( $target_context['webContext'] ),\n\t\t\t\t\t\t'data-wp-init' => 'callbacks.init',\n\t\t\t\t\t\t'data-wp-interactive' => '{{slugKebabCase}}',\n\t\t\t\t\t\t'data-wp-run--mounted' => 'callbacks.mounted',\n\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t\tob_start();\n\t\t\t\t?>\n<div <?php echo $wrapper_attributes; ?>>\n\t<h3 class=\"{{cssClassName}}__heading\"><?php echo esc_html( $target_context['heading'] ); ?></h3>\n\t<?php if ( '' !== $target_context['intro'] ) : ?>\n\t\t<p class=\"{{cssClassName}}__intro\"><?php echo esc_html( $target_context['intro'] ); ?></p>\n\t<?php endif; ?>\n\t<p\n\t\tclass=\"{{cssClassName}}__notice\"\n\t\tdata-wp-bind--hidden=\"!context.bootstrapReady || context.canWrite\"\n\t\thidden\n\t>\n\t\t<?php echo esc_html( $target_context['noticeMessage'] ); ?>\n\t</p>\n\t<p\n\t\tclass=\"{{cssClassName}}__error\"\n\t\trole=\"status\"\n\t\taria-live=\"polite\"\n\t\taria-atomic=\"true\"\n\t\tdata-wp-bind--hidden=\"!context.error\"\n\t\tdata-wp-text=\"context.error\"\n\t\thidden\n\t></p>\n\t<?php if ( ! empty( $target_context['showCount'] ) ) : ?>\n\t\t<div class=\"{{cssClassName}}__counter\">\n\t\t\t<span\n\t\t\t\tclass=\"{{cssClassName}}__count\"\n\t\t\t\trole=\"status\"\n\t\t\t\taria-live=\"polite\"\n\t\t\t\taria-atomic=\"true\"\n\t\t\t\tdata-wp-text=\"context.count\"\n\t\t\t>0</span>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tdisabled\n\t\t\t\tdata-wp-bind--disabled=\"!context.canWrite\"\n\t\t\t\tdata-wp-on--click=\"actions.increment\"\n\t\t\t>\n\t\t\t\t<?php echo esc_html( $target_context['buttonLabel'] ); ?>\n\t\t\t</button>\n\t\t</div>\n\t<?php endif; ?>\n\t<div class=\"{{cssClassName}}__items\">\n\t\t<?php echo wp_kses_post( $target_context['sanitizedContent'] ); ?>\n\t</div>\n</div>\n\t\t\t\t<?php\n\t\t\t\t$markup = (string) ob_get_clean();\n\t\t\t\tbreak;\n\t\t}\n\n\t\treturn apply_filters(\n\t\t\t'{{phpPrefix}}_{{slugSnakeCase}}_render_target_markup',\n\t\t\t$markup,\n\t\t\t$target,\n\t\t\t$target_context\n\t\t);\n\t}\n}\n";
9
+ /**
10
+ * SCSS template for compound parent block styles.
11
+ *
12
+ * @remarks Consumed by `buildCompoundArtifacts()` with `{{cssClassName}}`
13
+ * and `{{compoundChildCssClassName}}` scaffold placeholders.
14
+ */
15
+ export declare const COMPOUND_STYLE_TEMPLATE = ".{{cssClassName}} {\n\tborder: 1px solid #dcdcde;\n\tborder-radius: 12px;\n\tpadding: 1.25rem;\n\tbackground: #fff;\n}\n\n.{{cssClassName}}__heading {\n\tmargin: 0 0 0.5rem;\n\tfont-size: 1.2rem;\n}\n\n.{{cssClassName}}__intro {\n\tmargin: 0 0 1rem;\n\tcolor: #50575e;\n}\n\n.{{cssClassName}}__items {\n\tdisplay: grid;\n\tgap: 0.75rem;\n}\n\n.{{cssClassName}}[data-show-dividers='true'] .{{compoundChildCssClassName}} {\n\tborder-top: 1px solid #dcdcde;\n\tpadding-top: 0.75rem;\n}\n\n.{{cssClassName}}[data-show-dividers='true'] .{{compoundChildCssClassName}}:first-child {\n\tborder-top: 0;\n\tpadding-top: 0;\n}\n";
16
+ /**
17
+ * Main PHP `render.php` template for the compound parent block.
18
+ *
19
+ * @remarks Consumed by `buildCompoundArtifacts()` with scaffold placeholders
20
+ * for block identity, namespace, text domain, storage mode, persistence policy,
21
+ * CSS class names, and PHP-safe title output.
22
+ */
23
+ export declare const COMPOUND_PERSISTENCE_RENDER_TEMPLATE = "<?php\n/**\n * Dynamic render entry for the {{title}} compound parent block.\n *\n * @package {{pascalCase}}\n */\n\nif ( ! defined( 'ABSPATH' ) ) {\n\texit;\n}\n\n$validator_path = __DIR__ . '/typia-validator.php';\nif ( ! file_exists( $validator_path ) ) {\n\treturn '';\n}\n\n$validator = require $validator_path;\nif ( ! is_object( $validator ) || ! method_exists( $validator, 'apply_defaults' ) || ! method_exists( $validator, 'validate' ) ) {\n\treturn '';\n}\n\n$normalized = $validator->apply_defaults( is_array( $attributes ) ? $attributes : array() );\n$validation = $validator->validate( $normalized );\n$resource_key = isset( $normalized['resourceKey'] ) ? (string) $normalized['resourceKey'] : '';\n$heading = isset( $normalized['heading'] ) ? (string) $normalized['heading'] : {{titlePhpLiteral}};\n$intro = isset( $normalized['intro'] ) ? (string) $normalized['intro'] : '';\n$button_label = isset( $normalized['buttonLabel'] ) ? (string) $normalized['buttonLabel'] : 'Persist Count';\n$show_count = ! empty( $normalized['showCount'] );\n$show_dividers = ! empty( $normalized['showDividers'] );\n$post_id = is_object( $block ) && isset( $block->context['postId'] )\n\t? (int) $block->context['postId']\n\t: (int) get_queried_object_id();\n$storage_mode = '{{dataStorageMode}}';\n$persistence_policy = '{{persistencePolicy}}';\n\n$notice_message = 'authenticated' === $persistence_policy\n\t? __( 'Sign in to persist this counter.', '{{textDomain}}' )\n\t: __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );\n\nif ( empty( $validation['valid'] ) || '' === $resource_key ) {\n\treturn '';\n}\n\n{{phpPrefix}}_record_rendered_block_instance(\n\t(int) $post_id,\n\t'{{namespace}}/{{slugKebabCase}}',\n\t$resource_key\n);\n\n$context = array(\n\t'bootstrapReady' => false,\n\t'buttonLabel' => $button_label,\n\t'canWrite' => false,\n\t'client' => array(\n\t\t'writeExpiry' => 0,\n\t\t'writeNonce' => '',\n\t\t'writeToken' => '',\n\t),\n\t'count' => 0,\n\t'error' => '',\n\t'isBootstrapping' => false,\n\t'isLoading' => false,\n\t'isSaving' => false,\n\t'persistencePolicy' => $persistence_policy,\n\t'postId' => (int) $post_id,\n\t'resourceKey' => $resource_key,\n\t'showCount' => $show_count,\n\t'storage' => $storage_mode,\n);\n\n$allowed_inner_html = wp_kses_allowed_html( 'post' );\n\nforeach ( $allowed_inner_html as &$allowed_attributes ) {\n\tif ( ! is_array( $allowed_attributes ) ) {\n\t\tcontinue;\n\t}\n\n\t$allowed_attributes['data-wp-bind--disabled'] = true;\n\t$allowed_attributes['data-wp-bind--hidden'] = true;\n\t$allowed_attributes['data-wp-bind--value'] = true;\n\t$allowed_attributes['data-wp-class'] = true;\n\t$allowed_attributes['data-wp-class--active'] = true;\n\t$allowed_attributes['data-wp-context'] = true;\n\t$allowed_attributes['data-wp-init'] = true;\n\t$allowed_attributes['data-wp-interactive'] = true;\n\t$allowed_attributes['data-wp-on--click'] = true;\n\t$allowed_attributes['data-wp-on--mouseenter'] = true;\n\t$allowed_attributes['data-wp-on--mouseleave'] = true;\n\t$allowed_attributes['data-wp-run--mounted'] = true;\n\t$allowed_attributes['data-wp-style--width'] = true;\n\t$allowed_attributes['data-wp-text'] = true;\n}\nunset( $allowed_attributes );\n\n$sanitized_content = wp_kses( $content, $allowed_inner_html );\n\n$wrapper_attributes = get_block_wrapper_attributes(\n\tarray(\n\t\t'class' => '{{cssClassName}}',\n\t\t'data-show-dividers' => $show_dividers ? 'true' : 'false',\n\t\t'data-wp-context' => wp_json_encode( $context ),\n\t\t'data-wp-init' => 'callbacks.init',\n\t\t'data-wp-interactive' => '{{slugKebabCase}}',\n\t\t'data-wp-run--mounted' => 'callbacks.mounted',\n\t)\n);\n?>\n\n<div <?php echo $wrapper_attributes; ?>>\n\t<h3 class=\"{{cssClassName}}__heading\"><?php echo esc_html( $heading ); ?></h3>\n\t<?php if ( '' !== $intro ) : ?>\n\t\t<p class=\"{{cssClassName}}__intro\"><?php echo esc_html( $intro ); ?></p>\n\t<?php endif; ?>\n\t<p\n\t\tclass=\"{{cssClassName}}__notice\"\n\t\tdata-wp-bind--hidden=\"!context.bootstrapReady || context.canWrite\"\n\t\thidden\n\t>\n\t\t<?php echo esc_html( $notice_message ); ?>\n\t</p>\n\t<p\n\t\tclass=\"{{cssClassName}}__error\"\n\t\trole=\"status\"\n\t\taria-live=\"polite\"\n\t\taria-atomic=\"true\"\n\t\tdata-wp-bind--hidden=\"!context.error\"\n\t\tdata-wp-text=\"context.error\"\n\t\thidden\n\t></p>\n\t<?php if ( $show_count ) : ?>\n\t\t<div class=\"{{cssClassName}}__counter\">\n\t\t\t<span\n\t\t\t\tclass=\"{{cssClassName}}__count\"\n\t\t\t\trole=\"status\"\n\t\t\t\taria-live=\"polite\"\n\t\t\t\taria-atomic=\"true\"\n\t\t\t\tdata-wp-text=\"context.count\"\n\t\t\t>0</span>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tdisabled\n\t\t\t\tdata-wp-bind--disabled=\"!context.canWrite\"\n\t\t\t\tdata-wp-on--click=\"actions.increment\"\n\t\t\t>\n\t\t\t\t<?php echo esc_html( $button_label ); ?>\n\t\t\t</button>\n\t\t</div>\n\t<?php endif; ?>\n\t<div class=\"{{cssClassName}}__items\">\n\t\t<?php echo $sanitized_content; ?>\n\t</div>\n</div>\n";
@@ -0,0 +1,453 @@
1
+ /**
2
+ * PHP helper template for compound parent alternate render targets.
3
+ *
4
+ * @remarks Consumed by `buildCompoundArtifacts()` for email, MJML,
5
+ * plain-text, and web render entries with block identity, namespace, text
6
+ * domain, storage, policy, CSS class, and title placeholders.
7
+ */
8
+ export const COMPOUND_PERSISTENCE_RENDER_TARGETS_TEMPLATE = `<?php
9
+ /**
10
+ * Alternate render target helpers for the {{title}} compound parent block.
11
+ *
12
+ * @package {{pascalCase}}
13
+ */
14
+
15
+ if ( ! defined( 'ABSPATH' ) ) {
16
+ exit;
17
+ }
18
+
19
+ if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_build_render_context' ) ) {
20
+ function {{phpPrefix}}_{{slugSnakeCase}}_build_render_context( $attributes, $content, $block ) {
21
+ $validator_path = __DIR__ . '/typia-validator.php';
22
+ if ( ! file_exists( $validator_path ) ) {
23
+ return null;
24
+ }
25
+
26
+ $validator = require $validator_path;
27
+ if ( ! is_object( $validator ) || ! method_exists( $validator, 'apply_defaults' ) || ! method_exists( $validator, 'validate' ) ) {
28
+ return null;
29
+ }
30
+
31
+ $normalized = $validator->apply_defaults( is_array( $attributes ) ? $attributes : array() );
32
+ $validation = $validator->validate( $normalized );
33
+ $resource_key = isset( $normalized['resourceKey'] ) ? (string) $normalized['resourceKey'] : '';
34
+ $heading = isset( $normalized['heading'] ) ? (string) $normalized['heading'] : {{titlePhpLiteral}};
35
+ $intro = isset( $normalized['intro'] ) ? (string) $normalized['intro'] : '';
36
+ $button_label = isset( $normalized['buttonLabel'] ) ? (string) $normalized['buttonLabel'] : 'Persist Count';
37
+ $show_count = ! empty( $normalized['showCount'] );
38
+ $show_dividers = ! empty( $normalized['showDividers'] );
39
+ $post_id = is_object( $block ) && isset( $block->context['postId'] )
40
+ ? (int) $block->context['postId']
41
+ : (int) get_queried_object_id();
42
+ $storage_mode = '{{dataStorageMode}}';
43
+ $persistence_policy = '{{persistencePolicy}}';
44
+
45
+ $notice_message = 'authenticated' === $persistence_policy
46
+ ? __( 'Sign in to persist this counter.', '{{textDomain}}' )
47
+ : __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );
48
+
49
+ if ( empty( $validation['valid'] ) || '' === $resource_key ) {
50
+ return null;
51
+ }
52
+
53
+ {{phpPrefix}}_record_rendered_block_instance(
54
+ (int) $post_id,
55
+ '{{namespace}}/{{slugKebabCase}}',
56
+ $resource_key
57
+ );
58
+
59
+ $web_context = array(
60
+ 'bootstrapReady' => false,
61
+ 'buttonLabel' => $button_label,
62
+ 'canWrite' => false,
63
+ 'client' => array(
64
+ 'writeExpiry' => 0,
65
+ 'writeNonce' => '',
66
+ 'writeToken' => '',
67
+ ),
68
+ 'count' => 0,
69
+ 'error' => '',
70
+ 'isBootstrapping' => false,
71
+ 'isLoading' => false,
72
+ 'isSaving' => false,
73
+ 'persistencePolicy' => $persistence_policy,
74
+ 'postId' => (int) $post_id,
75
+ 'resourceKey' => $resource_key,
76
+ 'showCount' => $show_count,
77
+ 'storage' => $storage_mode,
78
+ );
79
+
80
+ $allowed_inner_html = wp_kses_allowed_html( 'post' );
81
+
82
+ foreach ( $allowed_inner_html as &$allowed_attributes ) {
83
+ if ( ! is_array( $allowed_attributes ) ) {
84
+ continue;
85
+ }
86
+
87
+ $allowed_attributes['data-wp-bind--disabled'] = true;
88
+ $allowed_attributes['data-wp-bind--hidden'] = true;
89
+ $allowed_attributes['data-wp-bind--value'] = true;
90
+ $allowed_attributes['data-wp-class'] = true;
91
+ $allowed_attributes['data-wp-class--active'] = true;
92
+ $allowed_attributes['data-wp-context'] = true;
93
+ $allowed_attributes['data-wp-init'] = true;
94
+ $allowed_attributes['data-wp-interactive'] = true;
95
+ $allowed_attributes['data-wp-on--click'] = true;
96
+ $allowed_attributes['data-wp-on--mouseenter'] = true;
97
+ $allowed_attributes['data-wp-on--mouseleave'] = true;
98
+ $allowed_attributes['data-wp-run--mounted'] = true;
99
+ $allowed_attributes['data-wp-style--width'] = true;
100
+ $allowed_attributes['data-wp-text'] = true;
101
+ }
102
+ unset( $allowed_attributes );
103
+
104
+ return array(
105
+ 'buttonLabel' => $button_label,
106
+ 'frontendClassName' => '{{cssClassName}}',
107
+ 'heading' => $heading,
108
+ 'intro' => $intro,
109
+ 'isVisible' => true,
110
+ 'normalized' => $normalized,
111
+ 'noticeMessage' => $notice_message,
112
+ 'postId' => (int) $post_id,
113
+ 'resourceKey' => $resource_key,
114
+ 'sanitizedContent' => wp_kses( $content, $allowed_inner_html ),
115
+ 'showCount' => $show_count,
116
+ 'showDividers' => $show_dividers,
117
+ 'title' => {{titleJson}},
118
+ 'webContext' => $web_context,
119
+ );
120
+ }
121
+ }
122
+
123
+ if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_render_target' ) ) {
124
+ function {{phpPrefix}}_{{slugSnakeCase}}_render_target( string $target, $attributes, $content = '', $block = null ): string {
125
+ $context = {{phpPrefix}}_{{slugSnakeCase}}_build_render_context( $attributes, $content, $block );
126
+ if ( null === $context ) {
127
+ return '';
128
+ }
129
+
130
+ $target_context = apply_filters(
131
+ '{{phpPrefix}}_{{slugSnakeCase}}_render_target_context',
132
+ $context,
133
+ $target,
134
+ $attributes,
135
+ $block
136
+ );
137
+
138
+ $markup = '';
139
+ switch ( $target ) {
140
+ case 'email':
141
+ $parts = array(
142
+ '<div class="' . esc_attr( $target_context['frontendClassName'] ) . '-email">',
143
+ '<h3>' . esc_html( $target_context['heading'] ) . '</h3>',
144
+ );
145
+ if ( '' !== $target_context['intro'] ) {
146
+ $parts[] = '<p>' . esc_html( $target_context['intro'] ) . '</p>';
147
+ }
148
+ if ( ! empty( $target_context['showCount'] ) ) {
149
+ $parts[] = '<p><strong>' . esc_html__( 'Count', '{{textDomain}}' ) . ':</strong> ' . esc_html( (string) $target_context['webContext']['count'] ) . '</p>';
150
+ }
151
+ $parts[] = wp_kses_post( $target_context['sanitizedContent'] );
152
+ $parts[] = '<p>' . esc_html( $target_context['noticeMessage'] ) . '</p>';
153
+ $parts[] = '</div>';
154
+ $markup = implode( '', $parts );
155
+ break;
156
+ case 'mjml':
157
+ $markup = '<mjml><mj-body><mj-section><mj-column><mj-text font-weight="700">' . esc_html( $target_context['heading'] ) . '</mj-text>';
158
+ if ( '' !== $target_context['intro'] ) {
159
+ $markup .= '<mj-text>' . esc_html( $target_context['intro'] ) . '</mj-text>';
160
+ }
161
+ if ( ! empty( $target_context['showCount'] ) ) {
162
+ $markup .= '<mj-text>' . esc_html__( 'Count', '{{textDomain}}' ) . ': ' . esc_html( (string) $target_context['webContext']['count'] ) . '</mj-text>';
163
+ }
164
+ $markup .= '<mj-text>' . esc_html( wp_strip_all_tags( $target_context['sanitizedContent'] ) ) . '</mj-text>';
165
+ $markup .= '<mj-text>' . esc_html( $target_context['noticeMessage'] ) . '</mj-text></mj-column></mj-section></mj-body></mjml>';
166
+ break;
167
+ case 'plain-text':
168
+ $markup = implode(
169
+ "\\n",
170
+ array_filter(
171
+ array(
172
+ $target_context['heading'],
173
+ $target_context['intro'],
174
+ wp_strip_all_tags( $target_context['sanitizedContent'] ),
175
+ ! empty( $target_context['showCount'] )
176
+ ? sprintf( '%s: %s', __( 'Count', '{{textDomain}}' ), (string) $target_context['webContext']['count'] )
177
+ : null,
178
+ $target_context['noticeMessage'],
179
+ ),
180
+ static fn( $value ) => is_string( $value ) && '' !== $value
181
+ )
182
+ );
183
+ break;
184
+ case 'web':
185
+ default:
186
+ $wrapper_attributes = get_block_wrapper_attributes(
187
+ array(
188
+ 'class' => '{{cssClassName}}',
189
+ 'data-show-dividers' => $target_context['showDividers'] ? 'true' : 'false',
190
+ 'data-wp-context' => wp_json_encode( $target_context['webContext'] ),
191
+ 'data-wp-init' => 'callbacks.init',
192
+ 'data-wp-interactive' => '{{slugKebabCase}}',
193
+ 'data-wp-run--mounted' => 'callbacks.mounted',
194
+ )
195
+ );
196
+ ob_start();
197
+ ?>
198
+ <div <?php echo $wrapper_attributes; ?>>
199
+ <h3 class="{{cssClassName}}__heading"><?php echo esc_html( $target_context['heading'] ); ?></h3>
200
+ <?php if ( '' !== $target_context['intro'] ) : ?>
201
+ <p class="{{cssClassName}}__intro"><?php echo esc_html( $target_context['intro'] ); ?></p>
202
+ <?php endif; ?>
203
+ <p
204
+ class="{{cssClassName}}__notice"
205
+ data-wp-bind--hidden="!context.bootstrapReady || context.canWrite"
206
+ hidden
207
+ >
208
+ <?php echo esc_html( $target_context['noticeMessage'] ); ?>
209
+ </p>
210
+ <p
211
+ class="{{cssClassName}}__error"
212
+ role="status"
213
+ aria-live="polite"
214
+ aria-atomic="true"
215
+ data-wp-bind--hidden="!context.error"
216
+ data-wp-text="context.error"
217
+ hidden
218
+ ></p>
219
+ <?php if ( ! empty( $target_context['showCount'] ) ) : ?>
220
+ <div class="{{cssClassName}}__counter">
221
+ <span
222
+ class="{{cssClassName}}__count"
223
+ role="status"
224
+ aria-live="polite"
225
+ aria-atomic="true"
226
+ data-wp-text="context.count"
227
+ >0</span>
228
+ <button
229
+ type="button"
230
+ disabled
231
+ data-wp-bind--disabled="!context.canWrite"
232
+ data-wp-on--click="actions.increment"
233
+ >
234
+ <?php echo esc_html( $target_context['buttonLabel'] ); ?>
235
+ </button>
236
+ </div>
237
+ <?php endif; ?>
238
+ <div class="{{cssClassName}}__items">
239
+ <?php echo wp_kses_post( $target_context['sanitizedContent'] ); ?>
240
+ </div>
241
+ </div>
242
+ <?php
243
+ $markup = (string) ob_get_clean();
244
+ break;
245
+ }
246
+
247
+ return apply_filters(
248
+ '{{phpPrefix}}_{{slugSnakeCase}}_render_target_markup',
249
+ $markup,
250
+ $target,
251
+ $target_context
252
+ );
253
+ }
254
+ }
255
+ `;
256
+ /**
257
+ * SCSS template for compound parent block styles.
258
+ *
259
+ * @remarks Consumed by `buildCompoundArtifacts()` with `{{cssClassName}}`
260
+ * and `{{compoundChildCssClassName}}` scaffold placeholders.
261
+ */
262
+ export const COMPOUND_STYLE_TEMPLATE = `.{{cssClassName}} {
263
+ border: 1px solid #dcdcde;
264
+ border-radius: 12px;
265
+ padding: 1.25rem;
266
+ background: #fff;
267
+ }
268
+
269
+ .{{cssClassName}}__heading {
270
+ margin: 0 0 0.5rem;
271
+ font-size: 1.2rem;
272
+ }
273
+
274
+ .{{cssClassName}}__intro {
275
+ margin: 0 0 1rem;
276
+ color: #50575e;
277
+ }
278
+
279
+ .{{cssClassName}}__items {
280
+ display: grid;
281
+ gap: 0.75rem;
282
+ }
283
+
284
+ .{{cssClassName}}[data-show-dividers='true'] .{{compoundChildCssClassName}} {
285
+ border-top: 1px solid #dcdcde;
286
+ padding-top: 0.75rem;
287
+ }
288
+
289
+ .{{cssClassName}}[data-show-dividers='true'] .{{compoundChildCssClassName}}:first-child {
290
+ border-top: 0;
291
+ padding-top: 0;
292
+ }
293
+ `;
294
+ /**
295
+ * Main PHP `render.php` template for the compound parent block.
296
+ *
297
+ * @remarks Consumed by `buildCompoundArtifacts()` with scaffold placeholders
298
+ * for block identity, namespace, text domain, storage mode, persistence policy,
299
+ * CSS class names, and PHP-safe title output.
300
+ */
301
+ export const COMPOUND_PERSISTENCE_RENDER_TEMPLATE = `<?php
302
+ /**
303
+ * Dynamic render entry for the {{title}} compound parent block.
304
+ *
305
+ * @package {{pascalCase}}
306
+ */
307
+
308
+ if ( ! defined( 'ABSPATH' ) ) {
309
+ exit;
310
+ }
311
+
312
+ $validator_path = __DIR__ . '/typia-validator.php';
313
+ if ( ! file_exists( $validator_path ) ) {
314
+ return '';
315
+ }
316
+
317
+ $validator = require $validator_path;
318
+ if ( ! is_object( $validator ) || ! method_exists( $validator, 'apply_defaults' ) || ! method_exists( $validator, 'validate' ) ) {
319
+ return '';
320
+ }
321
+
322
+ $normalized = $validator->apply_defaults( is_array( $attributes ) ? $attributes : array() );
323
+ $validation = $validator->validate( $normalized );
324
+ $resource_key = isset( $normalized['resourceKey'] ) ? (string) $normalized['resourceKey'] : '';
325
+ $heading = isset( $normalized['heading'] ) ? (string) $normalized['heading'] : {{titlePhpLiteral}};
326
+ $intro = isset( $normalized['intro'] ) ? (string) $normalized['intro'] : '';
327
+ $button_label = isset( $normalized['buttonLabel'] ) ? (string) $normalized['buttonLabel'] : 'Persist Count';
328
+ $show_count = ! empty( $normalized['showCount'] );
329
+ $show_dividers = ! empty( $normalized['showDividers'] );
330
+ $post_id = is_object( $block ) && isset( $block->context['postId'] )
331
+ ? (int) $block->context['postId']
332
+ : (int) get_queried_object_id();
333
+ $storage_mode = '{{dataStorageMode}}';
334
+ $persistence_policy = '{{persistencePolicy}}';
335
+
336
+ $notice_message = 'authenticated' === $persistence_policy
337
+ ? __( 'Sign in to persist this counter.', '{{textDomain}}' )
338
+ : __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );
339
+
340
+ if ( empty( $validation['valid'] ) || '' === $resource_key ) {
341
+ return '';
342
+ }
343
+
344
+ {{phpPrefix}}_record_rendered_block_instance(
345
+ (int) $post_id,
346
+ '{{namespace}}/{{slugKebabCase}}',
347
+ $resource_key
348
+ );
349
+
350
+ $context = array(
351
+ 'bootstrapReady' => false,
352
+ 'buttonLabel' => $button_label,
353
+ 'canWrite' => false,
354
+ 'client' => array(
355
+ 'writeExpiry' => 0,
356
+ 'writeNonce' => '',
357
+ 'writeToken' => '',
358
+ ),
359
+ 'count' => 0,
360
+ 'error' => '',
361
+ 'isBootstrapping' => false,
362
+ 'isLoading' => false,
363
+ 'isSaving' => false,
364
+ 'persistencePolicy' => $persistence_policy,
365
+ 'postId' => (int) $post_id,
366
+ 'resourceKey' => $resource_key,
367
+ 'showCount' => $show_count,
368
+ 'storage' => $storage_mode,
369
+ );
370
+
371
+ $allowed_inner_html = wp_kses_allowed_html( 'post' );
372
+
373
+ foreach ( $allowed_inner_html as &$allowed_attributes ) {
374
+ if ( ! is_array( $allowed_attributes ) ) {
375
+ continue;
376
+ }
377
+
378
+ $allowed_attributes['data-wp-bind--disabled'] = true;
379
+ $allowed_attributes['data-wp-bind--hidden'] = true;
380
+ $allowed_attributes['data-wp-bind--value'] = true;
381
+ $allowed_attributes['data-wp-class'] = true;
382
+ $allowed_attributes['data-wp-class--active'] = true;
383
+ $allowed_attributes['data-wp-context'] = true;
384
+ $allowed_attributes['data-wp-init'] = true;
385
+ $allowed_attributes['data-wp-interactive'] = true;
386
+ $allowed_attributes['data-wp-on--click'] = true;
387
+ $allowed_attributes['data-wp-on--mouseenter'] = true;
388
+ $allowed_attributes['data-wp-on--mouseleave'] = true;
389
+ $allowed_attributes['data-wp-run--mounted'] = true;
390
+ $allowed_attributes['data-wp-style--width'] = true;
391
+ $allowed_attributes['data-wp-text'] = true;
392
+ }
393
+ unset( $allowed_attributes );
394
+
395
+ $sanitized_content = wp_kses( $content, $allowed_inner_html );
396
+
397
+ $wrapper_attributes = get_block_wrapper_attributes(
398
+ array(
399
+ 'class' => '{{cssClassName}}',
400
+ 'data-show-dividers' => $show_dividers ? 'true' : 'false',
401
+ 'data-wp-context' => wp_json_encode( $context ),
402
+ 'data-wp-init' => 'callbacks.init',
403
+ 'data-wp-interactive' => '{{slugKebabCase}}',
404
+ 'data-wp-run--mounted' => 'callbacks.mounted',
405
+ )
406
+ );
407
+ ?>
408
+
409
+ <div <?php echo $wrapper_attributes; ?>>
410
+ <h3 class="{{cssClassName}}__heading"><?php echo esc_html( $heading ); ?></h3>
411
+ <?php if ( '' !== $intro ) : ?>
412
+ <p class="{{cssClassName}}__intro"><?php echo esc_html( $intro ); ?></p>
413
+ <?php endif; ?>
414
+ <p
415
+ class="{{cssClassName}}__notice"
416
+ data-wp-bind--hidden="!context.bootstrapReady || context.canWrite"
417
+ hidden
418
+ >
419
+ <?php echo esc_html( $notice_message ); ?>
420
+ </p>
421
+ <p
422
+ class="{{cssClassName}}__error"
423
+ role="status"
424
+ aria-live="polite"
425
+ aria-atomic="true"
426
+ data-wp-bind--hidden="!context.error"
427
+ data-wp-text="context.error"
428
+ hidden
429
+ ></p>
430
+ <?php if ( $show_count ) : ?>
431
+ <div class="{{cssClassName}}__counter">
432
+ <span
433
+ class="{{cssClassName}}__count"
434
+ role="status"
435
+ aria-live="polite"
436
+ aria-atomic="true"
437
+ data-wp-text="context.count"
438
+ >0</span>
439
+ <button
440
+ type="button"
441
+ disabled
442
+ data-wp-bind--disabled="!context.canWrite"
443
+ data-wp-on--click="actions.increment"
444
+ >
445
+ <?php echo esc_html( $button_label ); ?>
446
+ </button>
447
+ </div>
448
+ <?php endif; ?>
449
+ <div class="{{cssClassName}}__items">
450
+ <?php echo $sanitized_content; ?>
451
+ </div>
452
+ </div>
453
+ `;