@wp-typia/project-tools 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/runtime/alternate-render-targets.d.ts +5 -0
  2. package/dist/runtime/alternate-render-targets.js +29 -0
  3. package/dist/runtime/block-generator-service-core.d.ts +1 -1
  4. package/dist/runtime/block-generator-service-core.js +11 -7
  5. package/dist/runtime/block-generator-service-spec.d.ts +8 -1
  6. package/dist/runtime/block-generator-service-spec.js +43 -1
  7. package/dist/runtime/built-in-block-code-templates/compound-child.d.ts +1 -1
  8. package/dist/runtime/built-in-block-code-templates/compound-child.js +14 -9
  9. package/dist/runtime/built-in-block-code-templates/compound-parent.d.ts +2 -2
  10. package/dist/runtime/built-in-block-code-templates/compound-parent.js +100 -43
  11. package/dist/runtime/built-in-block-code-templates/compound-persistence.d.ts +1 -1
  12. package/dist/runtime/built-in-block-code-templates/compound-persistence.js +11 -8
  13. package/dist/runtime/built-in-block-non-ts-artifacts.js +505 -2
  14. package/dist/runtime/cli-add-block.d.ts +4 -1
  15. package/dist/runtime/cli-add-block.js +55 -26
  16. package/dist/runtime/cli-add-shared.d.ts +3 -1
  17. package/dist/runtime/cli-add-shared.js +12 -12
  18. package/dist/runtime/cli-core.d.ts +2 -0
  19. package/dist/runtime/cli-core.js +1 -0
  20. package/dist/runtime/cli-help.js +4 -3
  21. package/dist/runtime/cli-scaffold.d.ts +3 -1
  22. package/dist/runtime/cli-scaffold.js +88 -12
  23. package/dist/runtime/cli-templates.js +26 -1
  24. package/dist/runtime/cli-validation.d.ts +66 -0
  25. package/dist/runtime/cli-validation.js +92 -0
  26. package/dist/runtime/compound-inner-blocks.d.ts +78 -0
  27. package/dist/runtime/compound-inner-blocks.js +88 -0
  28. package/dist/runtime/index.d.ts +2 -0
  29. package/dist/runtime/index.js +1 -0
  30. package/dist/runtime/migration-command-surface.js +2 -0
  31. package/dist/runtime/package-versions.d.ts +1 -0
  32. package/dist/runtime/package-versions.js +12 -0
  33. package/dist/runtime/scaffold-answer-resolution.js +10 -6
  34. package/dist/runtime/scaffold-documents.js +22 -2
  35. package/dist/runtime/scaffold-identifiers.d.ts +17 -0
  36. package/dist/runtime/scaffold-identifiers.js +22 -0
  37. package/dist/runtime/scaffold-onboarding.js +21 -13
  38. package/dist/runtime/scaffold-template-variables.js +22 -0
  39. package/dist/runtime/scaffold.d.ts +16 -1
  40. package/dist/runtime/scaffold.js +7 -4
  41. package/dist/runtime/template-source.js +5 -3
  42. package/dist/runtime/workspace-project.js +1 -1
  43. package/package.json +7 -2
  44. package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +318 -18
@@ -0,0 +1,5 @@
1
+ export declare const ALTERNATE_RENDER_TARGET_IDS: readonly ["email", "mjml", "plain-text"];
2
+ export type AlternateRenderTargetId = (typeof ALTERNATE_RENDER_TARGET_IDS)[number];
3
+ export declare function isAlternateRenderTargetId(value: string): value is AlternateRenderTargetId;
4
+ export declare function parseAlternateRenderTargets(value?: string): AlternateRenderTargetId[];
5
+ export declare function formatAlternateRenderTargets(targets: readonly AlternateRenderTargetId[]): string;
@@ -0,0 +1,29 @@
1
+ export const ALTERNATE_RENDER_TARGET_IDS = [
2
+ "email",
3
+ "mjml",
4
+ "plain-text",
5
+ ];
6
+ export function isAlternateRenderTargetId(value) {
7
+ return ALTERNATE_RENDER_TARGET_IDS.includes(value);
8
+ }
9
+ export function parseAlternateRenderTargets(value) {
10
+ if (typeof value !== "string") {
11
+ return [];
12
+ }
13
+ const normalized = value
14
+ .split(",")
15
+ .map((entry) => entry.trim().toLowerCase())
16
+ .filter(Boolean);
17
+ if (normalized.length === 0) {
18
+ return [];
19
+ }
20
+ const deduped = Array.from(new Set(normalized));
21
+ const invalid = deduped.filter((entry) => !isAlternateRenderTargetId(entry));
22
+ if (invalid.length > 0) {
23
+ throw new Error(`Unsupported alternate render target${invalid.length > 1 ? "s" : ""} "${invalid.join(", ")}". Expected one of: ${ALTERNATE_RENDER_TARGET_IDS.join(", ")}.`);
24
+ }
25
+ return deduped;
26
+ }
27
+ export function formatAlternateRenderTargets(targets) {
28
+ return targets.join(", ");
29
+ }
@@ -1,7 +1,7 @@
1
1
  import { type ApplyBlockInput, type PlanBlockInput, type PlanBlockResult, type RenderBlockInput, type RenderBlockResult, type ValidateBlockInput, type ValidateBlockResult } from "./block-generator-service-spec.js";
2
2
  import type { ScaffoldProjectResult } from "./scaffold.js";
3
3
  export declare class BlockGeneratorService {
4
- plan({ allowExistingDir, answers, cwd, dataStorageMode, externalLayerId, externalLayerSource, externalLayerSourceLabel, noInstall, packageManager, persistencePolicy, projectDir, repositoryReference, templateId, variant, withMigrationUi, withTestPreset, withWpEnv, }: PlanBlockInput): Promise<PlanBlockResult>;
4
+ plan({ allowExistingDir, alternateRenderTargets, answers, cwd, dataStorageMode, externalLayerId, externalLayerSource, externalLayerSourceLabel, noInstall, packageManager, persistencePolicy, projectDir, repositoryReference, templateId, variant, withMigrationUi, withTestPreset, withWpEnv, }: PlanBlockInput): Promise<PlanBlockResult>;
5
5
  validate({ plan }: ValidateBlockInput): Promise<ValidateBlockResult>;
6
6
  render({ validated }: RenderBlockInput): Promise<RenderBlockResult>;
7
7
  apply({ rendered, installDependencies, onProgress, }: ApplyBlockInput): Promise<ScaffoldProjectResult>;
@@ -7,6 +7,7 @@ import { resolveTemplateSeed, parseTemplateLocator } from "./template-source.js"
7
7
  import { assertExternalTemplateLayersDoNotWriteProtectedOutputs, resolveExternalTemplateLayers, } from "./template-layers.js";
8
8
  import { getBuiltInTemplateOverlayDir, getBuiltInTemplateSharedLayerDirs, resolveBuiltInTemplateSource, resolveBuiltInTemplateSourceFromLayerDirs, } from "./template-builtins.js";
9
9
  import { buildTemplateVariablesFromBlockSpec, createBuiltInBlockSpec, } from "./block-generator-service-spec.js";
10
+ import { assertBuiltInTemplateVariantAllowed, assertExternalLayerCompositionOptions, } from "./cli-validation.js";
10
11
  const renderedArtifactCache = new WeakMap();
11
12
  function createVariablesFingerprint(variables) {
12
13
  return stableJsonStringify(variables);
@@ -82,9 +83,10 @@ async function runCleanupGroup(label, cleanups) {
82
83
  }
83
84
  }
84
85
  export class BlockGeneratorService {
85
- async plan({ allowExistingDir = false, answers, cwd = process.cwd(), dataStorageMode, externalLayerId, externalLayerSource, externalLayerSourceLabel, noInstall = false, packageManager, persistencePolicy, projectDir, repositoryReference, templateId, variant, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
86
+ async plan({ allowExistingDir = false, alternateRenderTargets, answers, cwd = process.cwd(), dataStorageMode, externalLayerId, externalLayerSource, externalLayerSourceLabel, noInstall = false, packageManager, persistencePolicy, projectDir, repositoryReference, templateId, variant, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
86
87
  return {
87
88
  spec: createBuiltInBlockSpec({
89
+ alternateRenderTargets,
88
90
  answers,
89
91
  dataStorageMode,
90
92
  persistencePolicy,
@@ -108,12 +110,14 @@ export class BlockGeneratorService {
108
110
  };
109
111
  }
110
112
  async validate({ plan }) {
111
- if (plan.target.externalLayerId && !plan.target.externalLayerSource) {
112
- throw new Error("externalLayerId requires externalLayerSource when composing built-in template layers.");
113
- }
114
- if (plan.target.variant) {
115
- throw new Error(`--variant is only supported for official external template configs. Received variant "${plan.target.variant}" for built-in template "${plan.spec.template.family}".`);
116
- }
113
+ assertExternalLayerCompositionOptions({
114
+ externalLayerId: plan.target.externalLayerId,
115
+ externalLayerSource: plan.target.externalLayerSource,
116
+ });
117
+ assertBuiltInTemplateVariantAllowed({
118
+ templateId: plan.spec.template.family,
119
+ variant: plan.target.variant,
120
+ });
117
121
  return plan;
118
122
  }
119
123
  async render({ validated }) {
@@ -1,14 +1,20 @@
1
1
  import type { PackageManagerId } from "./package-managers.js";
2
+ import { type AlternateRenderTargetId } from "./alternate-render-targets.js";
3
+ import { type CompoundInnerBlocksPresetId } from "./compound-inner-blocks.js";
2
4
  import { type BuiltInTemplateId } from "./template-registry.js";
3
5
  import type { InstallDependenciesOptions } from "./scaffold-apply-utils.js";
4
6
  import type { DataStorageMode, PersistencePolicy, ScaffoldAnswers, ScaffoldProgressEvent, ScaffoldTemplateVariables } from "./scaffold.js";
5
7
  export interface BlockSpec {
8
+ alternateRenderTargets: readonly AlternateRenderTargetId[];
6
9
  block: {
7
10
  namespace: string;
8
11
  phpPrefix: string;
9
12
  slug: string;
10
13
  textDomain: string;
11
14
  };
15
+ compound: {
16
+ innerBlocksPreset: CompoundInnerBlocksPresetId;
17
+ };
12
18
  metadata: {
13
19
  category: string;
14
20
  description: string;
@@ -57,6 +63,7 @@ export interface BlockGenerationTarget {
57
63
  }
58
64
  export interface PlanBlockInput {
59
65
  allowExistingDir?: boolean;
66
+ alternateRenderTargets?: string;
60
67
  answers: ScaffoldAnswers;
61
68
  cwd?: string;
62
69
  dataStorageMode?: DataStorageMode;
@@ -107,5 +114,5 @@ export interface ApplyBlockInput {
107
114
  installDependencies?: ((options: InstallDependenciesOptions) => Promise<void>) | undefined;
108
115
  onProgress?: ((event: ScaffoldProgressEvent) => void | Promise<void>) | undefined;
109
116
  }
110
- export declare function createBuiltInBlockSpec({ answers, dataStorageMode, persistencePolicy, templateId, withMigrationUi, withTestPreset, withWpEnv, }: Omit<PlanBlockInput, "allowExistingDir" | "cwd" | "noInstall" | "packageManager" | "projectDir" | "variant">): BlockSpec;
117
+ export declare function createBuiltInBlockSpec({ alternateRenderTargets, answers, dataStorageMode, persistencePolicy, templateId, withMigrationUi, withTestPreset, withWpEnv, }: Omit<PlanBlockInput, "allowExistingDir" | "cwd" | "noInstall" | "packageManager" | "projectDir" | "variant">): BlockSpec;
111
118
  export declare function buildTemplateVariablesFromBlockSpec(spec: BlockSpec): ScaffoldTemplateVariables;
@@ -1,5 +1,7 @@
1
1
  import { getPackageVersions } from "./package-versions.js";
2
2
  import { BUILTIN_BLOCK_METADATA_VERSION, COMPOUND_CHILD_BLOCK_METADATA_DEFAULTS, getBuiltInTemplateMetadataDefaults, } from "./template-defaults.js";
3
+ import { formatAlternateRenderTargets, parseAlternateRenderTargets, } from "./alternate-render-targets.js";
4
+ import { DEFAULT_COMPOUND_INNER_BLOCKS_PRESET_ID, getCompoundInnerBlocksPresetDefinition, } from "./compound-inner-blocks.js";
3
5
  import { getTemplateById, } from "./template-registry.js";
4
6
  import { toPascalCase, toSnakeCase, } from "./string-case.js";
5
7
  import { buildBlockCssClassName, buildFrontendCssClassName, resolveScaffoldIdentifiers, } from "./scaffold-identifiers.js";
@@ -33,7 +35,7 @@ const DEFAULT_QUERY_LOOP_ALLOWED_CONTROLS = [
33
35
  "author",
34
36
  "search",
35
37
  ];
36
- export function createBuiltInBlockSpec({ answers, dataStorageMode, persistencePolicy, templateId, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
38
+ export function createBuiltInBlockSpec({ alternateRenderTargets, answers, dataStorageMode, persistencePolicy, templateId, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
37
39
  const template = getTemplateById(templateId);
38
40
  const metadataDefaults = getBuiltInTemplateMetadataDefaults(templateId);
39
41
  const identifiers = resolveScaffoldIdentifiers({
@@ -44,8 +46,17 @@ export function createBuiltInBlockSpec({ answers, dataStorageMode, persistencePo
44
46
  });
45
47
  const resolvedDataStorageMode = dataStorageMode ?? answers.dataStorageMode;
46
48
  const resolvedPersistencePolicy = persistencePolicy ?? answers.persistencePolicy;
49
+ const parsedAlternateRenderTargets = parseAlternateRenderTargets(alternateRenderTargets);
50
+ const innerBlocksPreset = templateId === "compound"
51
+ ? (answers.compoundInnerBlocksPreset ??
52
+ DEFAULT_COMPOUND_INNER_BLOCKS_PRESET_ID)
53
+ : DEFAULT_COMPOUND_INNER_BLOCKS_PRESET_ID;
47
54
  return {
55
+ alternateRenderTargets: parsedAlternateRenderTargets,
48
56
  block: identifiers,
57
+ compound: {
58
+ innerBlocksPreset,
59
+ },
49
60
  metadata: {
50
61
  category: metadataDefaults.category,
51
62
  description: answers.description.trim(),
@@ -94,9 +105,15 @@ export function buildTemplateVariablesFromBlockSpec(spec) {
94
105
  const textDomain = spec.block.textDomain;
95
106
  const phpPrefix = spec.block.phpPrefix;
96
107
  const phpPrefixUpper = phpPrefix.toUpperCase();
108
+ const alternateRenderTargets = [...spec.alternateRenderTargets];
109
+ const hasAlternateEmailRenderTarget = alternateRenderTargets.includes("email");
110
+ const hasAlternateMjmlRenderTarget = alternateRenderTargets.includes("mjml");
111
+ const hasAlternatePlainTextRenderTarget = alternateRenderTargets.includes("plain-text");
97
112
  const compoundChildTitle = `${title} Item`;
98
113
  const cssClassName = buildBlockCssClassName(namespace, slug);
99
114
  const compoundChildCssClassName = buildBlockCssClassName(namespace, `${slug}-item`);
115
+ const compoundInnerBlocksPreset = spec.compound.innerBlocksPreset;
116
+ const compoundInnerBlocksPresetDefinition = getCompoundInnerBlocksPresetDefinition(compoundInnerBlocksPreset);
100
117
  const persistenceEnabled = spec.persistence.enabled;
101
118
  const dataStorageMode = persistenceEnabled ? spec.persistence.dataStorageMode : "custom-table";
102
119
  const persistencePolicy = persistenceEnabled
@@ -104,6 +121,8 @@ export function buildTemplateVariablesFromBlockSpec(spec) {
104
121
  : "authenticated";
105
122
  const queryVariationNamespace = `${namespace}/${slug}`;
106
123
  return {
124
+ alternateRenderTargetsCsv: formatAlternateRenderTargets(alternateRenderTargets),
125
+ alternateRenderTargetsJson: JSON.stringify(alternateRenderTargets),
107
126
  apiClientPackageVersion,
108
127
  author: spec.project.author,
109
128
  blockRuntimePackageVersion,
@@ -117,6 +136,29 @@ export function buildTemplateVariablesFromBlockSpec(spec) {
117
136
  compoundChildIcon: COMPOUND_CHILD_BLOCK_METADATA_DEFAULTS.icon,
118
137
  compoundChildTitleJson: JSON.stringify(compoundChildTitle),
119
138
  compoundPersistenceEnabled: spec.template.family === "compound" && persistenceEnabled ? "true" : "false",
139
+ compoundInnerBlocksDirectInsert: compoundInnerBlocksPresetDefinition.directInsert ? "true" : "false",
140
+ compoundInnerBlocksOrientation: compoundInnerBlocksPresetDefinition.orientation ?? "",
141
+ compoundInnerBlocksOrientationExpression: compoundInnerBlocksPresetDefinition.orientation
142
+ ? `'${compoundInnerBlocksPresetDefinition.orientation}'`
143
+ : "undefined",
144
+ compoundInnerBlocksPreset,
145
+ compoundInnerBlocksPresetDescription: compoundInnerBlocksPresetDefinition.description,
146
+ compoundInnerBlocksPresetLabel: compoundInnerBlocksPresetDefinition.label,
147
+ compoundInnerBlocksTemplateLockExpression: compoundInnerBlocksPresetDefinition.templateLock === false
148
+ ? "false"
149
+ : `'${compoundInnerBlocksPresetDefinition.templateLock}'`,
150
+ hasAlternateEmailRenderTarget: hasAlternateEmailRenderTarget
151
+ ? "true"
152
+ : "false",
153
+ hasAlternateMjmlRenderTarget: hasAlternateMjmlRenderTarget
154
+ ? "true"
155
+ : "false",
156
+ hasAlternatePlainTextRenderTarget: hasAlternatePlainTextRenderTarget
157
+ ? "true"
158
+ : "false",
159
+ hasAlternateRenderTargets: alternateRenderTargets.length > 0
160
+ ? "true"
161
+ : "false",
120
162
  projectToolsPackageVersion,
121
163
  cssClassName,
122
164
  dashCase: slug,
@@ -1,4 +1,4 @@
1
- export declare const COMPOUND_CHILD_EDIT_TEMPLATE = "import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';\nimport { InnerBlocks, RichText, useBlockProps } from '@wordpress/block-editor';\nimport { Notice } from '@wordpress/components';\nimport { __ } from '@wordpress/i18n';\n\nimport metadata from './block-metadata';\nimport {\n\tgetChildAllowedBlocks,\n\tgetChildTemplate,\n\thasNestedChildBlocks,\n} from '../{{slugKebabCase}}/children';\nimport { useTypiaValidation } from './hooks';\nimport type { {{pascalCase}}ItemAttributes } from './types';\nimport {\n\tcreateAttributeUpdater,\n\tvalidate{{pascalCase}}ItemAttributes,\n} from './validators';\n\ntype EditProps = BlockEditProps< {{pascalCase}}ItemAttributes >;\n\nexport default function Edit( {\n\tattributes,\n\tsetAttributes,\n}: EditProps ) {\n\tconst updateAttribute = createAttributeUpdater( attributes, setAttributes );\n\tconst { errorMessages, isValid } = useTypiaValidation(\n\t\tattributes,\n\t\tvalidate{{pascalCase}}ItemAttributes\n\t);\n\tconst nestedAllowedBlocks = getChildAllowedBlocks( metadata.name );\n\tconst nestedTemplate = getChildTemplate( metadata.name );\n\tconst showsNestedChildren = hasNestedChildBlocks( metadata.name );\n\n\treturn (\n\t\t<div { ...useBlockProps( { className: '{{compoundChildCssClassName}}' } ) }>\n\t\t\t<RichText\n\t\t\t\ttagName=\"h4\"\n\t\t\t\tclassName=\"{{compoundChildCssClassName}}__title\"\n\t\t\t\tvalue={ attributes.title ?? '' }\n\t\t\t\tonChange={ ( title ) => updateAttribute( 'title', title ) }\n\t\t\t\tplaceholder={ __( {{compoundChildTitleJson}}, '{{textDomain}}' ) }\n\t\t\t/>\n\t\t\t<RichText\n\t\t\t\ttagName=\"p\"\n\t\t\t\tclassName=\"{{compoundChildCssClassName}}__body\"\n\t\t\t\tvalue={ attributes.body ?? '' }\n\t\t\t\tonChange={ ( body ) => updateAttribute( 'body', body ) }\n\t\t\t\tplaceholder={ __( 'Add supporting details for this internal item.', '{{textDomain}}' ) }\n\t\t\t/>\n\t\t\t{ ! isValid && (\n\t\t\t\t<Notice status=\"error\" isDismissible={ false }>\n\t\t\t\t\t<ul>\n\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => <li key={ index }>{ error }</li> ) }\n\t\t\t\t\t</ul>\n\t\t\t\t</Notice>\n\t\t\t) }\n\t\t\t{ showsNestedChildren && (\n\t\t\t\t<div className=\"{{compoundChildCssClassName}}__children\">\n\t\t\t\t\t<InnerBlocks\n\t\t\t\t\t\tallowedBlocks={ nestedAllowedBlocks }\n\t\t\t\t\t\trenderAppender={ InnerBlocks.ButtonBlockAppender }\n\t\t\t\t\t\ttemplate={ nestedTemplate }\n\t\t\t\t\t\ttemplateLock={ false }\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t) }\n\t\t</div>\n\t);\n}\n";
1
+ export declare const COMPOUND_CHILD_EDIT_TEMPLATE = "import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';\nimport { InnerBlocks, RichText, useBlockProps } from '@wordpress/block-editor';\nimport { Notice } from '@wordpress/components';\nimport { __ } from '@wordpress/i18n';\n\nimport metadata from './block-metadata';\nimport {\n\tgetChildInnerBlocksPropsOptions,\n\thasNestedChildBlocks,\n} from '../{{slugKebabCase}}/children';\nimport { useTypiaValidation } from './hooks';\nimport type { {{pascalCase}}ItemAttributes } from './types';\nimport {\n\tcreateAttributeUpdater,\n\tvalidate{{pascalCase}}ItemAttributes,\n} from './validators';\n\ntype EditProps = BlockEditProps< {{pascalCase}}ItemAttributes >;\ntype CompoundInnerBlocksProps = Parameters< typeof InnerBlocks >[ 0 ] & {\n\tdefaultBlock?: [ string, Record< string, unknown > ];\n\tdirectInsert?: boolean;\n};\n\nconst TypedInnerBlocks = InnerBlocks as unknown as (\n\tprops: CompoundInnerBlocksProps\n) => ReturnType< typeof InnerBlocks >;\n\nexport default function Edit( {\n\tattributes,\n\tsetAttributes,\n}: EditProps ) {\n\tconst updateAttribute = createAttributeUpdater( attributes, setAttributes );\n\tconst { errorMessages, isValid } = useTypiaValidation(\n\t\tattributes,\n\t\tvalidate{{pascalCase}}ItemAttributes\n\t);\n\tconst nestedInnerBlocksPropsOptions = getChildInnerBlocksPropsOptions(\n\t\tmetadata.name\n\t);\n\tconst showsNestedChildren = hasNestedChildBlocks( metadata.name );\n\n\treturn (\n\t\t<div { ...useBlockProps( { className: '{{compoundChildCssClassName}}' } ) }>\n\t\t\t<RichText\n\t\t\t\ttagName=\"h4\"\n\t\t\t\tclassName=\"{{compoundChildCssClassName}}__title\"\n\t\t\t\tvalue={ attributes.title ?? '' }\n\t\t\t\tonChange={ ( title ) => updateAttribute( 'title', title ) }\n\t\t\t\tplaceholder={ __( {{compoundChildTitleJson}}, '{{textDomain}}' ) }\n\t\t\t/>\n\t\t\t<RichText\n\t\t\t\ttagName=\"p\"\n\t\t\t\tclassName=\"{{compoundChildCssClassName}}__body\"\n\t\t\t\tvalue={ attributes.body ?? '' }\n\t\t\t\tonChange={ ( body ) => updateAttribute( 'body', body ) }\n\t\t\t\tplaceholder={ __( 'Add supporting details for this internal item.', '{{textDomain}}' ) }\n\t\t\t/>\n\t\t\t{ ! isValid && (\n\t\t\t\t<Notice status=\"error\" isDismissible={ false }>\n\t\t\t\t\t<ul>\n\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => <li key={ index }>{ error }</li> ) }\n\t\t\t\t\t</ul>\n\t\t\t\t</Notice>\n\t\t\t) }\n\t\t\t{ showsNestedChildren && (\n\t\t\t\t<div className=\"{{compoundChildCssClassName}}__children\">\n\t\t\t\t\t<TypedInnerBlocks\n\t\t\t\t\t\t{ ...( nestedInnerBlocksPropsOptions ?? {} ) }\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t) }\n\t\t</div>\n\t);\n}\n";
2
2
  export declare const COMPOUND_CHILD_SAVE_TEMPLATE = "import { InnerBlocks, RichText, useBlockProps } from '@wordpress/block-editor';\n\nimport metadata from './block-metadata';\nimport { hasNestedChildBlocks } from '../{{slugKebabCase}}/children';\nimport type { {{pascalCase}}ItemAttributes } from './types';\n\nexport default function Save( {\n\tattributes,\n}: {\n\tattributes: {{pascalCase}}ItemAttributes;\n} ) {\n\tconst showsNestedChildren = hasNestedChildBlocks( metadata.name );\n\n\treturn (\n\t\t<div { ...useBlockProps.save( { className: '{{compoundChildCssClassName}}' } ) }>\n\t\t\t<RichText.Content\n\t\t\t\ttagName=\"h4\"\n\t\t\t\tclassName=\"{{compoundChildCssClassName}}__title\"\n\t\t\t\tvalue={ attributes.title }\n\t\t\t/>\n\t\t\t<RichText.Content\n\t\t\t\ttagName=\"p\"\n\t\t\t\tclassName=\"{{compoundChildCssClassName}}__body\"\n\t\t\t\tvalue={ attributes.body }\n\t\t\t/>\n\t\t\t{ showsNestedChildren && (\n\t\t\t\t<div className=\"{{compoundChildCssClassName}}__children\">\n\t\t\t\t\t<InnerBlocks.Content />\n\t\t\t\t</div>\n\t\t\t) }\n\t\t</div>\n\t);\n}\n";
3
3
  export declare const COMPOUND_CHILD_INDEX_TEMPLATE = "import {\n\tregisterScaffoldBlockType,\n\ttype BlockConfiguration,\n} from '@wp-typia/block-types/blocks/registration';\nimport {\n\tbuildScaffoldBlockRegistration,\n\tparseScaffoldBlockMetadata,\n} from '@wp-typia/block-runtime/blocks';\n\nimport Edit from './edit';\nimport Save from './save';\nimport metadata from './block-metadata';\nimport '../{{slugKebabCase}}/style.scss';\n\nimport type { {{pascalCase}}ItemAttributes } from './types';\n\nconst registration = buildScaffoldBlockRegistration(\n\tparseScaffoldBlockMetadata<BlockConfiguration< {{pascalCase}}ItemAttributes >>( metadata ),\n\t{\n\t\tedit: Edit,\n\t\tsave: Save,\n\t}\n);\n\nregisterScaffoldBlockType(registration.name, registration.settings);\n";
4
4
  export declare const COMPOUND_CHILD_VALIDATORS_TEMPLATE = "import typia from 'typia';\nimport currentManifest from './manifest-defaults-document';\nimport type {\n\t{{pascalCase}}ItemAttributes,\n\t{{pascalCase}}ItemValidationResult,\n} from './types';\nimport { createTemplateValidatorToolkit } from '../../validator-toolkit';\n\nconst scaffoldValidators = createTemplateValidatorToolkit< {{pascalCase}}ItemAttributes >( {\n\tassert: typia.createAssert< {{pascalCase}}ItemAttributes >(),\n\tclone: typia.misc.createClone< {{pascalCase}}ItemAttributes >() as (\n\t\tvalue: {{pascalCase}}ItemAttributes,\n\t) => {{pascalCase}}ItemAttributes,\n\tis: typia.createIs< {{pascalCase}}ItemAttributes >(),\n\tmanifest: currentManifest,\n\tprune: typia.misc.createPrune< {{pascalCase}}ItemAttributes >(),\n\trandom: typia.createRandom< {{pascalCase}}ItemAttributes >() as (\n\t\t...args: unknown[]\n\t) => {{pascalCase}}ItemAttributes,\n\tvalidate: typia.createValidate< {{pascalCase}}ItemAttributes >(),\n} );\n\nexport const validate{{pascalCase}}ItemAttributes =\n\tscaffoldValidators.validateAttributes as (\n\t\tattributes: unknown\n\t) => {{pascalCase}}ItemValidationResult;\n\nexport const validators = scaffoldValidators.validators;\n\nexport const sanitize{{pascalCase}}ItemAttributes =\n\tscaffoldValidators.sanitizeAttributes as (\n\t\tattributes: Partial< {{pascalCase}}ItemAttributes >\n\t) => {{pascalCase}}ItemAttributes;\n\nexport const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;\n";
@@ -5,8 +5,7 @@ import { __ } from '@wordpress/i18n';
5
5
 
6
6
  import metadata from './block-metadata';
7
7
  import {
8
- \tgetChildAllowedBlocks,
9
- \tgetChildTemplate,
8
+ \tgetChildInnerBlocksPropsOptions,
10
9
  \thasNestedChildBlocks,
11
10
  } from '../{{slugKebabCase}}/children';
12
11
  import { useTypiaValidation } from './hooks';
@@ -17,6 +16,14 @@ import {
17
16
  } from './validators';
18
17
 
19
18
  type EditProps = BlockEditProps< {{pascalCase}}ItemAttributes >;
19
+ type CompoundInnerBlocksProps = Parameters< typeof InnerBlocks >[ 0 ] & {
20
+ \tdefaultBlock?: [ string, Record< string, unknown > ];
21
+ \tdirectInsert?: boolean;
22
+ };
23
+
24
+ const TypedInnerBlocks = InnerBlocks as unknown as (
25
+ \tprops: CompoundInnerBlocksProps
26
+ ) => ReturnType< typeof InnerBlocks >;
20
27
 
21
28
  export default function Edit( {
22
29
  \tattributes,
@@ -27,8 +34,9 @@ export default function Edit( {
27
34
  \t\tattributes,
28
35
  \t\tvalidate{{pascalCase}}ItemAttributes
29
36
  \t);
30
- \tconst nestedAllowedBlocks = getChildAllowedBlocks( metadata.name );
31
- \tconst nestedTemplate = getChildTemplate( metadata.name );
37
+ \tconst nestedInnerBlocksPropsOptions = getChildInnerBlocksPropsOptions(
38
+ \t\tmetadata.name
39
+ \t);
32
40
  \tconst showsNestedChildren = hasNestedChildBlocks( metadata.name );
33
41
 
34
42
  \treturn (
@@ -56,11 +64,8 @@ export default function Edit( {
56
64
  \t\t\t) }
57
65
  \t\t\t{ showsNestedChildren && (
58
66
  \t\t\t\t<div className="{{compoundChildCssClassName}}__children">
59
- \t\t\t\t\t<InnerBlocks
60
- \t\t\t\t\t\tallowedBlocks={ nestedAllowedBlocks }
61
- \t\t\t\t\t\trenderAppender={ InnerBlocks.ButtonBlockAppender }
62
- \t\t\t\t\t\ttemplate={ nestedTemplate }
63
- \t\t\t\t\t\ttemplateLock={ false }
67
+ \t\t\t\t\t<TypedInnerBlocks
68
+ \t\t\t\t\t\t{ ...( nestedInnerBlocksPropsOptions ?? {} ) }
64
69
  \t\t\t\t\t/>
65
70
  \t\t\t\t</div>
66
71
  \t\t\t) }
@@ -1,6 +1,6 @@
1
- export declare const COMPOUND_PARENT_EDIT_TEMPLATE = "import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';\nimport { __ } from '@wordpress/i18n';\nimport {\n\tInspectorControls,\n\tInnerBlocks,\n\tRichText,\n\tuseBlockProps,\n} from '@wordpress/block-editor';\nimport { Notice, PanelBody, ToggleControl } from '@wordpress/components';\n\nimport {\n\tALLOWED_CHILD_BLOCKS,\n\tDEFAULT_CHILD_TEMPLATE,\n} from './children';\nimport { useTypiaValidation } from './hooks';\nimport type { {{pascalCase}}Attributes } from './types';\nimport {\n\tcreateAttributeUpdater,\n\tvalidate{{pascalCase}}Attributes,\n} from './validators';\n\ntype EditProps = BlockEditProps< {{pascalCase}}Attributes >;\n\nexport default function Edit( {\n\tattributes,\n\tsetAttributes,\n}: EditProps ) {\n\tconst { errorMessages, isValid } = useTypiaValidation(\n\t\tattributes,\n\t\tvalidate{{pascalCase}}Attributes\n\t);\n\tconst updateAttribute = createAttributeUpdater( attributes, setAttributes );\n\tconst blockProps = useBlockProps( {\n\t\tclassName: '{{cssClassName}}',\n\t} );\n\n\treturn (\n\t\t<>\n\t\t\t<InspectorControls>\n\t\t\t\t<PanelBody title={ __( 'Compound Settings', '{{textDomain}}' ) }>\n\t\t\t\t\t<ToggleControl\n\t\t\t\t\t\tlabel={ __( 'Show dividers between items', '{{textDomain}}' ) }\n\t\t\t\t\t\tchecked={ attributes.showDividers ?? true }\n\t\t\t\t\t\tonChange={ ( value ) => updateAttribute( 'showDividers', value ) }\n\t\t\t\t\t/>\n\t\t\t\t</PanelBody>\n\t\t\t\t{ ! isValid && (\n\t\t\t\t\t<PanelBody title={ __( 'Validation Errors', '{{textDomain}}' ) } initialOpen>\n\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => (\n\t\t\t\t\t\t\t<Notice key={ index } status=\"error\" isDismissible={ false }>\n\t\t\t\t\t\t\t\t{ error }\n\t\t\t\t\t\t\t</Notice>\n\t\t\t\t\t\t) ) }\n\t\t\t\t\t</PanelBody>\n\t\t\t\t) }\n\t\t\t</InspectorControls>\n\t\t\t<div { ...blockProps }>\n\t\t\t\t<RichText\n\t\t\t\t\ttagName=\"h3\"\n\t\t\t\t\tclassName=\"{{cssClassName}}__heading\"\n\t\t\t\t\tvalue={ attributes.heading }\n\t\t\t\t\tonChange={ ( heading ) => updateAttribute( 'heading', heading ) }\n\t\t\t\t\tplaceholder={ __( {{titleJson}}, '{{textDomain}}' ) }\n\t\t\t\t/>\n\t\t\t\t<RichText\n\t\t\t\t\ttagName=\"p\"\n\t\t\t\t\tclassName=\"{{cssClassName}}__intro\"\n\t\t\t\t\tvalue={ attributes.intro ?? '' }\n\t\t\t\t\tonChange={ ( intro ) => updateAttribute( 'intro', intro ) }\n\t\t\t\t\tplaceholder={ __(\n\t\t\t\t\t\t'Add and reorder internal items inside this compound block.',\n\t\t\t\t\t\t'{{textDomain}}'\n\t\t\t\t\t) }\n\t\t\t\t/>\n\t\t\t\t{ ! isValid && (\n\t\t\t\t\t<Notice status=\"error\" isDismissible={ false }>\n\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => <li key={ index }>{ error }</li> ) }\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t</Notice>\n\t\t\t\t) }\n\t\t\t\t<div className=\"{{cssClassName}}__items\">\n\t\t\t\t\t<InnerBlocks\n\t\t\t\t\t\tallowedBlocks={ ALLOWED_CHILD_BLOCKS }\n\t\t\t\t\t\trenderAppender={ InnerBlocks.ButtonBlockAppender }\n\t\t\t\t\t\ttemplate={ DEFAULT_CHILD_TEMPLATE }\n\t\t\t\t\t\ttemplateLock={ false }\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n";
1
+ export declare const COMPOUND_PARENT_EDIT_TEMPLATE = "import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';\nimport { __ } from '@wordpress/i18n';\nimport {\n\tInspectorControls,\n\tInnerBlocks,\n\tRichText,\n\tuseBlockProps,\n} from '@wordpress/block-editor';\nimport { Notice, PanelBody, ToggleControl } from '@wordpress/components';\n\nimport {\n\tgetRootInnerBlocksPropsOptions,\n} from './children';\nimport { useTypiaValidation } from './hooks';\nimport type { {{pascalCase}}Attributes } from './types';\nimport {\n\tcreateAttributeUpdater,\n\tvalidate{{pascalCase}}Attributes,\n} from './validators';\n\ntype EditProps = BlockEditProps< {{pascalCase}}Attributes >;\ntype CompoundInnerBlocksProps = Parameters< typeof InnerBlocks >[ 0 ] & {\n\tdefaultBlock?: [ string, Record< string, unknown > ];\n\tdirectInsert?: boolean;\n};\n\nconst TypedInnerBlocks = InnerBlocks as unknown as (\n\tprops: CompoundInnerBlocksProps\n) => ReturnType< typeof InnerBlocks >;\n\nexport default function Edit( {\n\tattributes,\n\tsetAttributes,\n}: EditProps ) {\n\tconst { errorMessages, isValid } = useTypiaValidation(\n\t\tattributes,\n\t\tvalidate{{pascalCase}}Attributes\n\t);\n\tconst updateAttribute = createAttributeUpdater( attributes, setAttributes );\n\tconst blockProps = useBlockProps( {\n\t\tclassName: '{{cssClassName}}',\n\t} );\n\tconst rootInnerBlocksPropsOptions = getRootInnerBlocksPropsOptions();\n\n\treturn (\n\t\t<>\n\t\t\t<InspectorControls>\n\t\t\t\t<PanelBody title={ __( 'Compound Settings', '{{textDomain}}' ) }>\n\t\t\t\t\t<ToggleControl\n\t\t\t\t\t\tlabel={ __( 'Show dividers between items', '{{textDomain}}' ) }\n\t\t\t\t\t\tchecked={ attributes.showDividers ?? true }\n\t\t\t\t\t\tonChange={ ( value ) => updateAttribute( 'showDividers', value ) }\n\t\t\t\t\t/>\n\t\t\t\t</PanelBody>\n\t\t\t\t{ ! isValid && (\n\t\t\t\t\t<PanelBody title={ __( 'Validation Errors', '{{textDomain}}' ) } initialOpen>\n\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => (\n\t\t\t\t\t\t\t<Notice key={ index } status=\"error\" isDismissible={ false }>\n\t\t\t\t\t\t\t\t{ error }\n\t\t\t\t\t\t\t</Notice>\n\t\t\t\t\t\t) ) }\n\t\t\t\t\t</PanelBody>\n\t\t\t\t) }\n\t\t\t</InspectorControls>\n\t\t\t<div { ...blockProps }>\n\t\t\t\t<RichText\n\t\t\t\t\ttagName=\"h3\"\n\t\t\t\t\tclassName=\"{{cssClassName}}__heading\"\n\t\t\t\t\tvalue={ attributes.heading }\n\t\t\t\t\tonChange={ ( heading ) => updateAttribute( 'heading', heading ) }\n\t\t\t\t\tplaceholder={ __( {{titleJson}}, '{{textDomain}}' ) }\n\t\t\t\t/>\n\t\t\t\t<RichText\n\t\t\t\t\ttagName=\"p\"\n\t\t\t\t\tclassName=\"{{cssClassName}}__intro\"\n\t\t\t\t\tvalue={ attributes.intro ?? '' }\n\t\t\t\t\tonChange={ ( intro ) => updateAttribute( 'intro', intro ) }\n\t\t\t\t\tplaceholder={ __(\n\t\t\t\t\t\t'Add and reorder internal items inside this compound block.',\n\t\t\t\t\t\t'{{textDomain}}'\n\t\t\t\t\t) }\n\t\t\t\t/>\n\t\t\t\t{ ! isValid && (\n\t\t\t\t\t<Notice status=\"error\" isDismissible={ false }>\n\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => <li key={ index }>{ error }</li> ) }\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t</Notice>\n\t\t\t\t) }\n\t\t\t\t<div className=\"{{cssClassName}}__items\">\n\t\t\t\t\t<TypedInnerBlocks { ...rootInnerBlocksPropsOptions } />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n";
2
2
  export declare const COMPOUND_PARENT_SAVE_TEMPLATE = "import { InnerBlocks, RichText, useBlockProps } from '@wordpress/block-editor';\n\nimport type { {{pascalCase}}Attributes } from './types';\n\nexport default function Save( {\n\tattributes,\n}: {\n\tattributes: {{pascalCase}}Attributes;\n} ) {\n\treturn (\n\t\t<div\n\t\t\t{ ...useBlockProps.save( {\n\t\t\t\tclassName: '{{cssClassName}}',\n\t\t\t\t'data-show-dividers': ( attributes.showDividers ?? true ) ? 'true' : 'false',\n\t\t\t} ) }\n\t\t>\n\t\t\t<RichText.Content\n\t\t\t\ttagName=\"h3\"\n\t\t\t\tclassName=\"{{cssClassName}}__heading\"\n\t\t\t\tvalue={ attributes.heading }\n\t\t\t/>\n\t\t\t<RichText.Content\n\t\t\t\ttagName=\"p\"\n\t\t\t\tclassName=\"{{cssClassName}}__intro\"\n\t\t\t\tvalue={ attributes.intro ?? '' }\n\t\t\t/>\n\t\t\t<div className=\"{{cssClassName}}__items\">\n\t\t\t\t<InnerBlocks.Content />\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n";
3
3
  export declare const COMPOUND_PARENT_INDEX_TEMPLATE = "import {\n\tregisterScaffoldBlockType,\n\ttype BlockConfiguration,\n} from '@wp-typia/block-types/blocks/registration';\nimport {\n\tbuildScaffoldBlockRegistration,\n\tparseScaffoldBlockMetadata,\n} from '@wp-typia/block-runtime/blocks';\n\nimport Edit from './edit';\nimport Save from './save';\nimport metadata from './block-metadata';\nimport './style.scss';\n\nimport type { {{pascalCase}}Attributes } from './types';\n\nconst registration = buildScaffoldBlockRegistration(\n\tparseScaffoldBlockMetadata<BlockConfiguration< {{pascalCase}}Attributes >>( metadata ),\n\t{\n\t\tedit: Edit,\n\t\tsave: Save,\n\t}\n);\n\nregisterScaffoldBlockType(registration.name, registration.settings);\n";
4
4
  export declare const COMPOUND_LOCAL_HOOKS_TEMPLATE = "export {\n\tformatValidationError,\n\tformatValidationErrors,\n\tuseTypiaValidation,\n} from '../../hooks';\n\nexport type {\n\tTypiaValidationError,\n\tValidationResult,\n\tValidationState,\n} from '../../hooks';\n";
5
5
  export declare const COMPOUND_PARENT_VALIDATORS_TEMPLATE = "import typia from 'typia';\nimport currentManifest from './manifest-defaults-document';\nimport type {\n\t{{pascalCase}}Attributes,\n\t{{pascalCase}}ValidationResult,\n} from './types';\nimport { createTemplateValidatorToolkit } from '../../validator-toolkit';\n\nconst scaffoldValidators = createTemplateValidatorToolkit< {{pascalCase}}Attributes >( {\n\tassert: typia.createAssert< {{pascalCase}}Attributes >(),\n\tclone: typia.misc.createClone< {{pascalCase}}Attributes >() as (\n\t\tvalue: {{pascalCase}}Attributes,\n\t) => {{pascalCase}}Attributes,\n\tis: typia.createIs< {{pascalCase}}Attributes >(),\n\tmanifest: currentManifest,\n\tprune: typia.misc.createPrune< {{pascalCase}}Attributes >(),\n\trandom: typia.createRandom< {{pascalCase}}Attributes >() as (\n\t\t...args: unknown[]\n\t) => {{pascalCase}}Attributes,\n\tvalidate: typia.createValidate< {{pascalCase}}Attributes >(),\n} );\n\nexport const validate{{pascalCase}}Attributes =\n\tscaffoldValidators.validateAttributes as (\n\t\tattributes: unknown\n\t) => {{pascalCase}}ValidationResult;\n\nexport const validators = scaffoldValidators.validators;\n\nexport const sanitize{{pascalCase}}Attributes =\n\tscaffoldValidators.sanitizeAttributes as (\n\t\tattributes: Partial< {{pascalCase}}Attributes >\n\t) => {{pascalCase}}Attributes;\n\nexport const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;\n";
6
- export declare const COMPOUND_CHILDREN_TEMPLATE = "import type { BlockTemplate } from '@wp-typia/block-types/blocks/registration';\n\nexport const DEFAULT_CHILD_BLOCK_NAME = '{{namespace}}/{{slugKebabCase}}-item';\n\nexport interface CompoundChildSpec {\n\tancestorKeys: string[];\n\tblockName: string;\n\tbodyPlaceholder: string;\n\tcontainer: boolean;\n\tfolderSlug: string;\n\tkey: string;\n\tplacement: 'nested' | 'root';\n\tsupportsInserter: boolean;\n\ttemplateInstances: Array< Record< string, unknown > >;\n\ttitle: string;\n}\n\nconst ROOT_BLOCK_NAME = '{{namespace}}/{{slugKebabCase}}';\n\nexport const COMPOUND_CHILD_SPECS: CompoundChildSpec[] = [\n\t{\n\t\tancestorKeys: [],\n\t\tblockName: DEFAULT_CHILD_BLOCK_NAME,\n\t\tbodyPlaceholder: 'Add supporting details for this internal item.',\n\t\tcontainer: false,\n\t\tfolderSlug: '{{slugKebabCase}}-item',\n\t\tkey: 'item',\n\t\tplacement: 'root',\n\t\tsupportsInserter: false,\n\t\ttemplateInstances: [\n\t\t\t{\n\t\t\t\tbody: 'Add supporting details for the first internal item.',\n\t\t\t\ttitle: 'First Item',\n\t\t\t},\n\t\t\t{\n\t\t\t\tbody: 'Add supporting details for the second internal item.',\n\t\t\t\ttitle: 'Second Item',\n\t\t\t},\n\t\t],\n\t\ttitle: '{{compoundChildTitle}}',\n\t},\n\t// add-child: insert new child specs here\n];\n\nfunction getChildSpecByKey( key: string ): CompoundChildSpec | undefined {\n\treturn COMPOUND_CHILD_SPECS.find( ( spec ) => spec.key === key );\n}\n\nfunction buildTemplateEntriesForSpec( spec: CompoundChildSpec ): BlockTemplate {\n\tconst nestedTemplate = buildNestedTemplateForKey( spec.key );\n\n\treturn spec.templateInstances.map( ( attributes ) =>\n\t\tnestedTemplate.length > 0\n\t\t\t? [ spec.blockName, attributes, nestedTemplate ]\n\t\t\t: [ spec.blockName, attributes ]\n\t);\n}\n\nfunction buildNestedTemplateForKey( key: string ): BlockTemplate {\n\treturn COMPOUND_CHILD_SPECS.filter(\n\t\t( spec ) =>\n\t\t\tspec.placement === 'nested' &&\n\t\t\tspec.ancestorKeys[ spec.ancestorKeys.length - 1 ] === key\n\t).flatMap( ( spec ) => buildTemplateEntriesForSpec( spec ) );\n}\n\nexport const ALLOWED_CHILD_BLOCKS = COMPOUND_CHILD_SPECS.filter(\n\t( spec ) => spec.placement === 'root'\n).map( ( spec ) => spec.blockName );\n\nexport const DEFAULT_CHILD_TEMPLATE: BlockTemplate =\n\tCOMPOUND_CHILD_SPECS.filter( ( spec ) => spec.placement === 'root' ).flatMap(\n\t\t( spec ) => buildTemplateEntriesForSpec( spec )\n\t);\n\nexport function getChildSpec( blockName: string ): CompoundChildSpec | undefined {\n\treturn COMPOUND_CHILD_SPECS.find( ( spec ) => spec.blockName === blockName );\n}\n\nexport function getChildAllowedBlocks(\n\tblockName: string\n): string[] | undefined {\n\tconst childSpec = getChildSpec( blockName );\n\tif ( ! childSpec ) {\n\t\treturn undefined;\n\t}\n\n\tconst directDescendants = COMPOUND_CHILD_SPECS.filter(\n\t\t( spec ) =>\n\t\t\tspec.placement === 'nested' &&\n\t\t\tspec.ancestorKeys[ spec.ancestorKeys.length - 1 ] === childSpec.key\n\t).map( ( spec ) => spec.blockName );\n\n\tif ( directDescendants.length > 0 ) {\n\t\treturn directDescendants;\n\t}\n\n\treturn childSpec.container ? [] : undefined;\n}\n\nexport function getChildAncestorBlockNames(\n\tblockName: string\n): string[] | undefined {\n\tconst childSpec = getChildSpec( blockName );\n\tif ( ! childSpec || childSpec.ancestorKeys.length === 0 ) {\n\t\treturn undefined;\n\t}\n\n\treturn childSpec.ancestorKeys.flatMap( ( key ) => {\n\t\tconst ancestorSpec = getChildSpecByKey( key );\n\t\treturn ancestorSpec ? [ ancestorSpec.blockName ] : [];\n\t} );\n}\n\nexport function getChildTemplate(\n\tblockName: string\n): BlockTemplate | undefined {\n\tconst childSpec = getChildSpec( blockName );\n\tif ( ! childSpec ) {\n\t\treturn undefined;\n\t}\n\n\tconst nestedTemplate = buildNestedTemplateForKey( childSpec.key );\n\tif ( nestedTemplate.length > 0 ) {\n\t\treturn nestedTemplate;\n\t}\n\n\treturn childSpec.container ? [] : undefined;\n}\n\nexport function hasNestedChildBlocks( blockName: string ): boolean {\n\tconst childSpec = getChildSpec( blockName );\n\tif ( ! childSpec ) {\n\t\treturn false;\n\t}\n\n\treturn childSpec.container || buildNestedTemplateForKey( childSpec.key ).length > 0;\n}\n\nexport function isRootCompoundChildBlock( blockName: string ): boolean {\n\tconst childSpec = getChildSpec( blockName );\n\treturn childSpec?.placement === 'root';\n}\n\nexport { ROOT_BLOCK_NAME };\n";
6
+ export declare const COMPOUND_CHILDREN_TEMPLATE = "import type { BlockTemplate } from '@wp-typia/block-types/blocks/registration';\nimport { InnerBlocks } from '@wordpress/block-editor';\n\nexport const DEFAULT_CHILD_BLOCK_NAME = '{{namespace}}/{{slugKebabCase}}-item';\n\nexport interface CompoundChildSpec {\n\tancestorKeys: string[];\n\tblockName: string;\n\tbodyPlaceholder: string;\n\tcontainer: boolean;\n\tfolderSlug: string;\n\tkey: string;\n\tplacement: 'nested' | 'root';\n\tsupportsInserter: boolean;\n\ttemplateInstances: Array< Record< string, unknown > >;\n\ttitle: string;\n}\n\nexport interface CompoundInnerBlocksConfig {\n\tdefaultBlock?: [ string, Record< string, unknown > ];\n\tdirectInsert: boolean;\n\torientation?: 'horizontal' | 'vertical';\n\ttemplate?: BlockTemplate;\n\ttemplateLock: false | 'insert' | 'all';\n}\n\nexport type CompoundInnerBlocksPropsOptions = CompoundInnerBlocksConfig & {\n\trenderAppender?: typeof InnerBlocks.ButtonBlockAppender;\n};\n\nexport const ROOT_INNER_BLOCKS_PRESET_ID = '{{compoundInnerBlocksPreset}}';\nexport const ROOT_INNER_BLOCKS_PRESET_DESCRIPTION =\n\t'{{compoundInnerBlocksPresetDescription}}';\n\nconst BASE_INNER_BLOCKS_CONFIG: Omit<\n\tCompoundInnerBlocksConfig,\n\t'defaultBlock' | 'template'\n> = {\n\tdirectInsert: {{compoundInnerBlocksDirectInsert}},\n\torientation: {{compoundInnerBlocksOrientationExpression}},\n\ttemplateLock: {{compoundInnerBlocksTemplateLockExpression}},\n};\n\nconst ROOT_BLOCK_NAME = '{{namespace}}/{{slugKebabCase}}';\n\nexport const COMPOUND_CHILD_SPECS: CompoundChildSpec[] = [\n\t{\n\t\tancestorKeys: [],\n\t\tblockName: DEFAULT_CHILD_BLOCK_NAME,\n\t\tbodyPlaceholder: 'Add supporting details for this internal item.',\n\t\tcontainer: false,\n\t\tfolderSlug: '{{slugKebabCase}}-item',\n\t\tkey: 'item',\n\t\tplacement: 'root',\n\t\tsupportsInserter: false,\n\t\ttemplateInstances: [\n\t\t\t{\n\t\t\t\tbody: 'Add supporting details for the first internal item.',\n\t\t\t\ttitle: 'First Item',\n\t\t\t},\n\t\t\t{\n\t\t\t\tbody: 'Add supporting details for the second internal item.',\n\t\t\t\ttitle: 'Second Item',\n\t\t\t},\n\t\t],\n\t\ttitle: '{{compoundChildTitle}}',\n\t},\n\t// add-child: insert new child specs here\n];\n\nfunction buildTemplateEntriesForSpec( spec: CompoundChildSpec ): BlockTemplate {\n\tconst nestedTemplate = buildNestedTemplateForKey( spec.key );\n\n\treturn spec.templateInstances.map( ( attributes ) =>\n\t\tnestedTemplate.length > 0\n\t\t\t? [ spec.blockName, attributes, nestedTemplate ]\n\t\t\t: [ spec.blockName, attributes ]\n\t);\n}\n\nfunction buildNestedTemplateForKey( key: string ): BlockTemplate {\n\treturn COMPOUND_CHILD_SPECS.filter(\n\t\t( spec ) =>\n\t\t\tspec.placement === 'nested' &&\n\t\t\tspec.ancestorKeys[ spec.ancestorKeys.length - 1 ] === key\n\t).flatMap( ( spec ) => buildTemplateEntriesForSpec( spec ) );\n}\n\nexport const DEFAULT_CHILD_TEMPLATE: BlockTemplate =\n\tCOMPOUND_CHILD_SPECS.filter( ( spec ) => spec.placement === 'root' ).flatMap(\n\t\t( spec ) => buildTemplateEntriesForSpec( spec )\n\t);\n\nfunction buildDefaultBlockEntry(\n\ttemplate?: BlockTemplate\n): [ string, Record< string, unknown > ] | undefined {\n\tif (\n\t\t! BASE_INNER_BLOCKS_CONFIG.directInsert ||\n\t\t! Array.isArray( template ) ||\n\t\ttemplate.length === 0 ||\n\t\ttypeof template[ 0 ]?.[ 0 ] !== 'string'\n\t) {\n\t\treturn undefined;\n\t}\n\n\treturn [ template[ 0 ][ 0 ], {} ];\n}\n\nfunction buildInnerBlocksPropsOptions(\n\tconfig: CompoundInnerBlocksConfig\n): CompoundInnerBlocksPropsOptions {\n\treturn {\n\t\t...config,\n\t\trenderAppender:\n\t\t\tconfig.templateLock === 'all'\n\t\t\t\t? undefined\n\t\t\t\t: InnerBlocks.ButtonBlockAppender,\n\t};\n}\n\nexport function getRootInnerBlocksConfig(): CompoundInnerBlocksConfig {\n\treturn {\n\t\t...BASE_INNER_BLOCKS_CONFIG,\n\t\tdefaultBlock: buildDefaultBlockEntry( DEFAULT_CHILD_TEMPLATE ),\n\t\ttemplate: DEFAULT_CHILD_TEMPLATE,\n\t};\n}\n\nexport function getRootInnerBlocksPropsOptions(): CompoundInnerBlocksPropsOptions {\n\treturn buildInnerBlocksPropsOptions( getRootInnerBlocksConfig() );\n}\n\nexport function getChildSpec( blockName: string ): CompoundChildSpec | undefined {\n\treturn COMPOUND_CHILD_SPECS.find( ( spec ) => spec.blockName === blockName );\n}\n\nexport function getChildTemplate(\n\tblockName: string\n): BlockTemplate | undefined {\n\tconst childSpec = getChildSpec( blockName );\n\tif ( ! childSpec ) {\n\t\treturn undefined;\n\t}\n\n\tconst nestedTemplate = buildNestedTemplateForKey( childSpec.key );\n\tif ( nestedTemplate.length > 0 ) {\n\t\treturn nestedTemplate;\n\t}\n\n\treturn childSpec.container ? [] : undefined;\n}\n\nexport function getChildInnerBlocksConfig(\n\tblockName: string\n): CompoundInnerBlocksConfig | undefined {\n\tconst childSpec = getChildSpec( blockName );\n\tif ( ! childSpec ) {\n\t\treturn undefined;\n\t}\n\n\tconst template = getChildTemplate( blockName );\n\n\tif ( ! childSpec.container && ! template ) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\t...BASE_INNER_BLOCKS_CONFIG,\n\t\tdefaultBlock: buildDefaultBlockEntry( template ),\n\t\ttemplate,\n\t};\n}\n\nexport function getChildInnerBlocksPropsOptions(\n\tblockName: string\n): CompoundInnerBlocksPropsOptions | undefined {\n\tconst config = getChildInnerBlocksConfig( blockName );\n\tif ( ! config ) {\n\t\treturn undefined;\n\t}\n\n\treturn buildInnerBlocksPropsOptions( config );\n}\n\nexport function hasNestedChildBlocks( blockName: string ): boolean {\n\tconst childSpec = getChildSpec( blockName );\n\tif ( ! childSpec ) {\n\t\treturn false;\n\t}\n\n\treturn childSpec.container || buildNestedTemplateForKey( childSpec.key ).length > 0;\n}\n\nexport function isRootCompoundChildBlock( blockName: string ): boolean {\n\tconst childSpec = getChildSpec( blockName );\n\treturn childSpec?.placement === 'root';\n}\n\nexport { ROOT_BLOCK_NAME };\n";
@@ -9,8 +9,7 @@ import {
9
9
  import { Notice, PanelBody, ToggleControl } from '@wordpress/components';
10
10
 
11
11
  import {
12
- ALLOWED_CHILD_BLOCKS,
13
- DEFAULT_CHILD_TEMPLATE,
12
+ getRootInnerBlocksPropsOptions,
14
13
  } from './children';
15
14
  import { useTypiaValidation } from './hooks';
16
15
  import type { {{pascalCase}}Attributes } from './types';
@@ -20,6 +19,14 @@ import {
20
19
  } from './validators';
21
20
 
22
21
  type EditProps = BlockEditProps< {{pascalCase}}Attributes >;
22
+ type CompoundInnerBlocksProps = Parameters< typeof InnerBlocks >[ 0 ] & {
23
+ defaultBlock?: [ string, Record< string, unknown > ];
24
+ directInsert?: boolean;
25
+ };
26
+
27
+ const TypedInnerBlocks = InnerBlocks as unknown as (
28
+ props: CompoundInnerBlocksProps
29
+ ) => ReturnType< typeof InnerBlocks >;
23
30
 
24
31
  export default function Edit( {
25
32
  attributes,
@@ -33,6 +40,7 @@ export default function Edit( {
33
40
  const blockProps = useBlockProps( {
34
41
  className: '{{cssClassName}}',
35
42
  } );
43
+ const rootInnerBlocksPropsOptions = getRootInnerBlocksPropsOptions();
36
44
 
37
45
  return (
38
46
  <>
@@ -80,12 +88,7 @@ export default function Edit( {
80
88
  </Notice>
81
89
  ) }
82
90
  <div className="{{cssClassName}}__items">
83
- <InnerBlocks
84
- allowedBlocks={ ALLOWED_CHILD_BLOCKS }
85
- renderAppender={ InnerBlocks.ButtonBlockAppender }
86
- template={ DEFAULT_CHILD_TEMPLATE }
87
- templateLock={ false }
88
- />
91
+ <TypedInnerBlocks { ...rootInnerBlocksPropsOptions } />
89
92
  </div>
90
93
  </div>
91
94
  </>
@@ -200,6 +203,7 @@ export const sanitize{{pascalCase}}Attributes =
200
203
  export const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;
201
204
  `;
202
205
  export const COMPOUND_CHILDREN_TEMPLATE = `import type { BlockTemplate } from '@wp-typia/block-types/blocks/registration';
206
+ import { InnerBlocks } from '@wordpress/block-editor';
203
207
 
204
208
  export const DEFAULT_CHILD_BLOCK_NAME = '{{namespace}}/{{slugKebabCase}}-item';
205
209
 
@@ -216,6 +220,31 @@ export interface CompoundChildSpec {
216
220
  \ttitle: string;
217
221
  }
218
222
 
223
+ export interface CompoundInnerBlocksConfig {
224
+ \tdefaultBlock?: [ string, Record< string, unknown > ];
225
+ \tdirectInsert: boolean;
226
+ \torientation?: 'horizontal' | 'vertical';
227
+ \ttemplate?: BlockTemplate;
228
+ \ttemplateLock: false | 'insert' | 'all';
229
+ }
230
+
231
+ export type CompoundInnerBlocksPropsOptions = CompoundInnerBlocksConfig & {
232
+ \trenderAppender?: typeof InnerBlocks.ButtonBlockAppender;
233
+ };
234
+
235
+ export const ROOT_INNER_BLOCKS_PRESET_ID = '{{compoundInnerBlocksPreset}}';
236
+ export const ROOT_INNER_BLOCKS_PRESET_DESCRIPTION =
237
+ \t'{{compoundInnerBlocksPresetDescription}}';
238
+
239
+ const BASE_INNER_BLOCKS_CONFIG: Omit<
240
+ \tCompoundInnerBlocksConfig,
241
+ \t'defaultBlock' | 'template'
242
+ > = {
243
+ \tdirectInsert: {{compoundInnerBlocksDirectInsert}},
244
+ \torientation: {{compoundInnerBlocksOrientationExpression}},
245
+ \ttemplateLock: {{compoundInnerBlocksTemplateLockExpression}},
246
+ };
247
+
219
248
  const ROOT_BLOCK_NAME = '{{namespace}}/{{slugKebabCase}}';
220
249
 
221
250
  export const COMPOUND_CHILD_SPECS: CompoundChildSpec[] = [
@@ -243,10 +272,6 @@ export const COMPOUND_CHILD_SPECS: CompoundChildSpec[] = [
243
272
  \t// add-child: insert new child specs here
244
273
  ];
245
274
 
246
- function getChildSpecByKey( key: string ): CompoundChildSpec | undefined {
247
- \treturn COMPOUND_CHILD_SPECS.find( ( spec ) => spec.key === key );
248
- }
249
-
250
275
  function buildTemplateEntriesForSpec( spec: CompoundChildSpec ): BlockTemplate {
251
276
  \tconst nestedTemplate = buildNestedTemplateForKey( spec.key );
252
277
 
@@ -265,68 +290,100 @@ function buildNestedTemplateForKey( key: string ): BlockTemplate {
265
290
  \t).flatMap( ( spec ) => buildTemplateEntriesForSpec( spec ) );
266
291
  }
267
292
 
268
- export const ALLOWED_CHILD_BLOCKS = COMPOUND_CHILD_SPECS.filter(
269
- \t( spec ) => spec.placement === 'root'
270
- ).map( ( spec ) => spec.blockName );
271
-
272
293
  export const DEFAULT_CHILD_TEMPLATE: BlockTemplate =
273
294
  \tCOMPOUND_CHILD_SPECS.filter( ( spec ) => spec.placement === 'root' ).flatMap(
274
295
  \t\t( spec ) => buildTemplateEntriesForSpec( spec )
275
296
  \t);
276
297
 
298
+ function buildDefaultBlockEntry(
299
+ \ttemplate?: BlockTemplate
300
+ ): [ string, Record< string, unknown > ] | undefined {
301
+ \tif (
302
+ \t\t! BASE_INNER_BLOCKS_CONFIG.directInsert ||
303
+ \t\t! Array.isArray( template ) ||
304
+ \t\ttemplate.length === 0 ||
305
+ \t\ttypeof template[ 0 ]?.[ 0 ] !== 'string'
306
+ \t) {
307
+ \t\treturn undefined;
308
+ \t}
309
+
310
+ \treturn [ template[ 0 ][ 0 ], {} ];
311
+ }
312
+
313
+ function buildInnerBlocksPropsOptions(
314
+ \tconfig: CompoundInnerBlocksConfig
315
+ ): CompoundInnerBlocksPropsOptions {
316
+ \treturn {
317
+ \t\t...config,
318
+ \t\trenderAppender:
319
+ \t\t\tconfig.templateLock === 'all'
320
+ \t\t\t\t? undefined
321
+ \t\t\t\t: InnerBlocks.ButtonBlockAppender,
322
+ \t};
323
+ }
324
+
325
+ export function getRootInnerBlocksConfig(): CompoundInnerBlocksConfig {
326
+ \treturn {
327
+ \t\t...BASE_INNER_BLOCKS_CONFIG,
328
+ \t\tdefaultBlock: buildDefaultBlockEntry( DEFAULT_CHILD_TEMPLATE ),
329
+ \t\ttemplate: DEFAULT_CHILD_TEMPLATE,
330
+ \t};
331
+ }
332
+
333
+ export function getRootInnerBlocksPropsOptions(): CompoundInnerBlocksPropsOptions {
334
+ \treturn buildInnerBlocksPropsOptions( getRootInnerBlocksConfig() );
335
+ }
336
+
277
337
  export function getChildSpec( blockName: string ): CompoundChildSpec | undefined {
278
338
  \treturn COMPOUND_CHILD_SPECS.find( ( spec ) => spec.blockName === blockName );
279
339
  }
280
340
 
281
- export function getChildAllowedBlocks(
341
+ export function getChildTemplate(
282
342
  \tblockName: string
283
- ): string[] | undefined {
343
+ ): BlockTemplate | undefined {
284
344
  \tconst childSpec = getChildSpec( blockName );
285
345
  \tif ( ! childSpec ) {
286
346
  \t\treturn undefined;
287
347
  \t}
288
348
 
289
- \tconst directDescendants = COMPOUND_CHILD_SPECS.filter(
290
- \t\t( spec ) =>
291
- \t\t\tspec.placement === 'nested' &&
292
- \t\t\tspec.ancestorKeys[ spec.ancestorKeys.length - 1 ] === childSpec.key
293
- \t).map( ( spec ) => spec.blockName );
294
-
295
- \tif ( directDescendants.length > 0 ) {
296
- \t\treturn directDescendants;
349
+ \tconst nestedTemplate = buildNestedTemplateForKey( childSpec.key );
350
+ \tif ( nestedTemplate.length > 0 ) {
351
+ \t\treturn nestedTemplate;
297
352
  \t}
298
353
 
299
354
  \treturn childSpec.container ? [] : undefined;
300
355
  }
301
356
 
302
- export function getChildAncestorBlockNames(
357
+ export function getChildInnerBlocksConfig(
303
358
  \tblockName: string
304
- ): string[] | undefined {
359
+ ): CompoundInnerBlocksConfig | undefined {
305
360
  \tconst childSpec = getChildSpec( blockName );
306
- \tif ( ! childSpec || childSpec.ancestorKeys.length === 0 ) {
361
+ \tif ( ! childSpec ) {
307
362
  \t\treturn undefined;
308
363
  \t}
309
364
 
310
- \treturn childSpec.ancestorKeys.flatMap( ( key ) => {
311
- \t\tconst ancestorSpec = getChildSpecByKey( key );
312
- \t\treturn ancestorSpec ? [ ancestorSpec.blockName ] : [];
313
- \t} );
314
- }
365
+ \tconst template = getChildTemplate( blockName );
315
366
 
316
- export function getChildTemplate(
317
- \tblockName: string
318
- ): BlockTemplate | undefined {
319
- \tconst childSpec = getChildSpec( blockName );
320
- \tif ( ! childSpec ) {
367
+ \tif ( ! childSpec.container && ! template ) {
321
368
  \t\treturn undefined;
322
369
  \t}
323
370
 
324
- \tconst nestedTemplate = buildNestedTemplateForKey( childSpec.key );
325
- \tif ( nestedTemplate.length > 0 ) {
326
- \t\treturn nestedTemplate;
371
+ \treturn {
372
+ \t\t...BASE_INNER_BLOCKS_CONFIG,
373
+ \t\tdefaultBlock: buildDefaultBlockEntry( template ),
374
+ \t\ttemplate,
375
+ \t};
376
+ }
377
+
378
+ export function getChildInnerBlocksPropsOptions(
379
+ \tblockName: string
380
+ ): CompoundInnerBlocksPropsOptions | undefined {
381
+ \tconst config = getChildInnerBlocksConfig( blockName );
382
+ \tif ( ! config ) {
383
+ \t\treturn undefined;
327
384
  \t}
328
385
 
329
- \treturn childSpec.container ? [] : undefined;
386
+ \treturn buildInnerBlocksPropsOptions( config );
330
387
  }
331
388
 
332
389
  export function hasNestedChildBlocks( blockName: string ): boolean {
@@ -1,4 +1,4 @@
1
- export declare const COMPOUND_PERSISTENCE_PARENT_EDIT_TEMPLATE = "import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';\nimport { __ } from '@wordpress/i18n';\nimport {\n\tInspectorControls,\n\tInnerBlocks,\n\tRichText,\n\tuseBlockProps,\n} from '@wordpress/block-editor';\nimport {\n\tNotice,\n\tPanelBody,\n\tTextControl,\n\tToggleControl,\n} from '@wordpress/components';\n\nimport {\n\tALLOWED_CHILD_BLOCKS,\n\tDEFAULT_CHILD_TEMPLATE,\n} from './children';\nimport { useTypiaValidation } from './hooks';\nimport type { {{pascalCase}}Attributes } from './types';\nimport {\n\tcreateAttributeUpdater,\n\tvalidate{{pascalCase}}Attributes,\n} from './validators';\n\ntype EditProps = BlockEditProps< {{pascalCase}}Attributes >;\n\nexport default function Edit( {\n\tattributes,\n\tsetAttributes,\n}: EditProps ) {\n\tconst { errorMessages, isValid } = useTypiaValidation(\n\t\tattributes,\n\t\tvalidate{{pascalCase}}Attributes\n\t);\n\tconst updateAttribute = createAttributeUpdater( attributes, setAttributes );\n\tconst blockProps = useBlockProps( {\n\t\tclassName: '{{cssClassName}}',\n\t} );\n\n\treturn (\n\t\t<>\n\t\t\t<InspectorControls>\n\t\t\t\t<PanelBody title={ __( 'Compound Settings', '{{textDomain}}' ) }>\n\t\t\t\t\t<ToggleControl\n\t\t\t\t\t\tlabel={ __( 'Show dividers between items', '{{textDomain}}' ) }\n\t\t\t\t\t\tchecked={ attributes.showDividers ?? true }\n\t\t\t\t\t\tonChange={ ( value ) => updateAttribute( 'showDividers', value ) }\n\t\t\t\t\t/>\n\t\t\t\t\t<ToggleControl\n\t\t\t\t\t\tlabel={ __( 'Show persisted count', '{{textDomain}}' ) }\n\t\t\t\t\t\tchecked={ attributes.showCount ?? true }\n\t\t\t\t\t\tonChange={ ( value ) => updateAttribute( 'showCount', value ) }\n\t\t\t\t\t/>\n\t\t\t\t\t<TextControl\n\t\t\t\t\t\tlabel={ __( 'Button label', '{{textDomain}}' ) }\n\t\t\t\t\t\tvalue={ attributes.buttonLabel ?? 'Persist Count' }\n\t\t\t\t\t\tonChange={ ( buttonLabel ) => updateAttribute( 'buttonLabel', buttonLabel ) }\n\t\t\t\t\t/>\n\t\t\t\t\t<TextControl\n\t\t\t\t\t\tlabel={ __( 'Resource key', '{{textDomain}}' ) }\n\t\t\t\t\t\tvalue={ attributes.resourceKey ?? '' }\n\t\t\t\t\t\tonChange={ ( resourceKey ) => updateAttribute( 'resourceKey', resourceKey ) }\n\t\t\t\t\t\thelp={ __( 'Stable key used by the persisted counter endpoint.', '{{textDomain}}' ) }\n\t\t\t\t\t/>\n\t\t\t\t\t<Notice status=\"info\" isDismissible={ false }>\n\t\t\t\t\t\t{ __( 'Storage mode: {{dataStorageMode}}', '{{textDomain}}' ) }\n\t\t\t\t\t</Notice>\n\t\t\t\t\t<Notice status=\"info\" isDismissible={ false }>\n\t\t\t\t\t\t{ __( 'Persistence policy: {{persistencePolicy}}', '{{textDomain}}' ) }\n\t\t\t\t\t</Notice>\n\t\t\t\t</PanelBody>\n\t\t\t\t{ ! isValid && (\n\t\t\t\t\t<PanelBody title={ __( 'Validation Errors', '{{textDomain}}' ) } initialOpen>\n\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => (\n\t\t\t\t\t\t\t<Notice key={ index } status=\"error\" isDismissible={ false }>\n\t\t\t\t\t\t\t\t{ error }\n\t\t\t\t\t\t\t</Notice>\n\t\t\t\t\t\t) ) }\n\t\t\t\t\t</PanelBody>\n\t\t\t\t) }\n\t\t\t</InspectorControls>\n\t\t\t<div { ...blockProps }>\n\t\t\t\t<RichText\n\t\t\t\t\ttagName=\"h3\"\n\t\t\t\t\tclassName=\"{{cssClassName}}__heading\"\n\t\t\t\t\tvalue={ attributes.heading }\n\t\t\t\t\tonChange={ ( heading ) => updateAttribute( 'heading', heading ) }\n\t\t\t\t\tplaceholder={ __( {{titleJson}}, '{{textDomain}}' ) }\n\t\t\t\t/>\n\t\t\t\t<RichText\n\t\t\t\t\ttagName=\"p\"\n\t\t\t\t\tclassName=\"{{cssClassName}}__intro\"\n\t\t\t\t\tvalue={ attributes.intro ?? '' }\n\t\t\t\t\tonChange={ ( intro ) => updateAttribute( 'intro', intro ) }\n\t\t\t\t\tplaceholder={ __(\n\t\t\t\t\t\t'Add and reorder internal items inside this compound block.',\n\t\t\t\t\t\t'{{textDomain}}'\n\t\t\t\t\t) }\n\t\t\t\t/>\n\t\t\t\t{ ! isValid && (\n\t\t\t\t\t<Notice status=\"error\" isDismissible={ false }>\n\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => <li key={ index }>{ error }</li> ) }\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t</Notice>\n\t\t\t\t) }\n\t\t\t\t<p className=\"{{cssClassName}}__meta\">\n\t\t\t\t\t{ __( 'Resource key:', '{{textDomain}}' ) } { attributes.resourceKey || '\u2014' }\n\t\t\t\t</p>\n\t\t\t\t<div className=\"{{cssClassName}}__items\">\n\t\t\t\t\t<InnerBlocks\n\t\t\t\t\t\tallowedBlocks={ ALLOWED_CHILD_BLOCKS }\n\t\t\t\t\t\trenderAppender={ InnerBlocks.ButtonBlockAppender }\n\t\t\t\t\t\ttemplate={ DEFAULT_CHILD_TEMPLATE }\n\t\t\t\t\t\ttemplateLock={ false }\n\t\t\t\t\t/>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n";
1
+ export declare const COMPOUND_PERSISTENCE_PARENT_EDIT_TEMPLATE = "import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';\nimport { __ } from '@wordpress/i18n';\nimport {\n\tInspectorControls,\n\tInnerBlocks,\n\tRichText,\n\tuseBlockProps,\n} from '@wordpress/block-editor';\nimport {\n\tNotice,\n\tPanelBody,\n\tTextControl,\n\tToggleControl,\n} from '@wordpress/components';\n\nimport {\n\tgetRootInnerBlocksPropsOptions,\n} from './children';\nimport { useTypiaValidation } from './hooks';\nimport type { {{pascalCase}}Attributes } from './types';\nimport {\n\tcreateAttributeUpdater,\n\tvalidate{{pascalCase}}Attributes,\n} from './validators';\n\ntype EditProps = BlockEditProps< {{pascalCase}}Attributes >;\ntype CompoundInnerBlocksProps = Parameters< typeof InnerBlocks >[ 0 ] & {\n\tdefaultBlock?: [ string, Record< string, unknown > ];\n\tdirectInsert?: boolean;\n};\n\nconst TypedInnerBlocks = InnerBlocks as unknown as (\n\tprops: CompoundInnerBlocksProps\n) => ReturnType< typeof InnerBlocks >;\n\nexport default function Edit( {\n\tattributes,\n\tsetAttributes,\n}: EditProps ) {\n\tconst { errorMessages, isValid } = useTypiaValidation(\n\t\tattributes,\n\t\tvalidate{{pascalCase}}Attributes\n\t);\n\tconst updateAttribute = createAttributeUpdater( attributes, setAttributes );\n\tconst blockProps = useBlockProps( {\n\t\tclassName: '{{cssClassName}}',\n\t} );\n\tconst rootInnerBlocksPropsOptions = getRootInnerBlocksPropsOptions();\n\n\treturn (\n\t\t<>\n\t\t\t<InspectorControls>\n\t\t\t\t<PanelBody title={ __( 'Compound Settings', '{{textDomain}}' ) }>\n\t\t\t\t\t<ToggleControl\n\t\t\t\t\t\tlabel={ __( 'Show dividers between items', '{{textDomain}}' ) }\n\t\t\t\t\t\tchecked={ attributes.showDividers ?? true }\n\t\t\t\t\t\tonChange={ ( value ) => updateAttribute( 'showDividers', value ) }\n\t\t\t\t\t/>\n\t\t\t\t\t<ToggleControl\n\t\t\t\t\t\tlabel={ __( 'Show persisted count', '{{textDomain}}' ) }\n\t\t\t\t\t\tchecked={ attributes.showCount ?? true }\n\t\t\t\t\t\tonChange={ ( value ) => updateAttribute( 'showCount', value ) }\n\t\t\t\t\t/>\n\t\t\t\t\t<TextControl\n\t\t\t\t\t\tlabel={ __( 'Button label', '{{textDomain}}' ) }\n\t\t\t\t\t\tvalue={ attributes.buttonLabel ?? 'Persist Count' }\n\t\t\t\t\t\tonChange={ ( buttonLabel ) => updateAttribute( 'buttonLabel', buttonLabel ) }\n\t\t\t\t\t/>\n\t\t\t\t\t<TextControl\n\t\t\t\t\t\tlabel={ __( 'Resource key', '{{textDomain}}' ) }\n\t\t\t\t\t\tvalue={ attributes.resourceKey ?? '' }\n\t\t\t\t\t\tonChange={ ( resourceKey ) => updateAttribute( 'resourceKey', resourceKey ) }\n\t\t\t\t\t\thelp={ __( 'Stable key used by the persisted counter endpoint.', '{{textDomain}}' ) }\n\t\t\t\t\t/>\n\t\t\t\t\t<Notice status=\"info\" isDismissible={ false }>\n\t\t\t\t\t\t{ __( 'Storage mode: {{dataStorageMode}}', '{{textDomain}}' ) }\n\t\t\t\t\t</Notice>\n\t\t\t\t\t<Notice status=\"info\" isDismissible={ false }>\n\t\t\t\t\t\t{ __( 'Persistence policy: {{persistencePolicy}}', '{{textDomain}}' ) }\n\t\t\t\t\t</Notice>\n\t\t\t\t</PanelBody>\n\t\t\t\t{ ! isValid && (\n\t\t\t\t\t<PanelBody title={ __( 'Validation Errors', '{{textDomain}}' ) } initialOpen>\n\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => (\n\t\t\t\t\t\t\t<Notice key={ index } status=\"error\" isDismissible={ false }>\n\t\t\t\t\t\t\t\t{ error }\n\t\t\t\t\t\t\t</Notice>\n\t\t\t\t\t\t) ) }\n\t\t\t\t\t</PanelBody>\n\t\t\t\t) }\n\t\t\t</InspectorControls>\n\t\t\t<div { ...blockProps }>\n\t\t\t\t<RichText\n\t\t\t\t\ttagName=\"h3\"\n\t\t\t\t\tclassName=\"{{cssClassName}}__heading\"\n\t\t\t\t\tvalue={ attributes.heading }\n\t\t\t\t\tonChange={ ( heading ) => updateAttribute( 'heading', heading ) }\n\t\t\t\t\tplaceholder={ __( {{titleJson}}, '{{textDomain}}' ) }\n\t\t\t\t/>\n\t\t\t\t<RichText\n\t\t\t\t\ttagName=\"p\"\n\t\t\t\t\tclassName=\"{{cssClassName}}__intro\"\n\t\t\t\t\tvalue={ attributes.intro ?? '' }\n\t\t\t\t\tonChange={ ( intro ) => updateAttribute( 'intro', intro ) }\n\t\t\t\t\tplaceholder={ __(\n\t\t\t\t\t\t'Add and reorder internal items inside this compound block.',\n\t\t\t\t\t\t'{{textDomain}}'\n\t\t\t\t\t) }\n\t\t\t\t/>\n\t\t\t\t{ ! isValid && (\n\t\t\t\t\t<Notice status=\"error\" isDismissible={ false }>\n\t\t\t\t\t\t<ul>\n\t\t\t\t\t\t\t{ errorMessages.map( ( error, index ) => <li key={ index }>{ error }</li> ) }\n\t\t\t\t\t\t</ul>\n\t\t\t\t\t</Notice>\n\t\t\t\t) }\n\t\t\t\t<p className=\"{{cssClassName}}__meta\">\n\t\t\t\t\t{ __( 'Resource key:', '{{textDomain}}' ) } { attributes.resourceKey || '\u2014' }\n\t\t\t\t</p>\n\t\t\t\t<div className=\"{{cssClassName}}__items\">\n\t\t\t\t\t<TypedInnerBlocks { ...rootInnerBlocksPropsOptions } />\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</>\n\t);\n}\n";
2
2
  export declare const COMPOUND_PERSISTENCE_PARENT_SAVE_TEMPLATE = "export default function Save() {\n\treturn null;\n}\n";
3
3
  export declare const COMPOUND_PERSISTENCE_PARENT_VALIDATORS_TEMPLATE = "import typia from 'typia';\nimport currentManifest from './manifest-defaults-document';\nimport type {\n\t{{pascalCase}}Attributes,\n\t{{pascalCase}}ValidationResult,\n} from './types';\nimport { generateResourceKey } from '@wp-typia/block-runtime/identifiers';\nimport { createTemplateValidatorToolkit } from '../../validator-toolkit';\n\nconst scaffoldValidators = createTemplateValidatorToolkit< {{pascalCase}}Attributes >( {\n\tassert: typia.createAssert< {{pascalCase}}Attributes >(),\n\tclone: typia.misc.createClone< {{pascalCase}}Attributes >() as (\n\t\tvalue: {{pascalCase}}Attributes,\n\t) => {{pascalCase}}Attributes,\n\tis: typia.createIs< {{pascalCase}}Attributes >(),\n\tmanifest: currentManifest,\n\tprune: typia.misc.createPrune< {{pascalCase}}Attributes >(),\n\trandom: typia.createRandom< {{pascalCase}}Attributes >() as (\n\t\t...args: unknown[]\n\t) => {{pascalCase}}Attributes,\n\tfinalize: ( normalized ) => ( {\n\t\t...normalized,\n\t\tresourceKey:\n\t\t\tnormalized.resourceKey && normalized.resourceKey.length > 0\n\t\t\t\t? normalized.resourceKey\n\t\t\t\t: generateResourceKey( '{{slugKebabCase}}' ),\n\t} ),\n\tvalidate: typia.createValidate< {{pascalCase}}Attributes >(),\n} );\n\nexport const validators = scaffoldValidators.validators;\n\nexport const validate{{pascalCase}}Attributes =\n\tscaffoldValidators.validateAttributes as (\n\t\tattributes: unknown\n\t) => {{pascalCase}}ValidationResult;\n\nexport const sanitize{{pascalCase}}Attributes =\n\tscaffoldValidators.sanitizeAttributes as (\n\t\tattributes: Partial< {{pascalCase}}Attributes >\n\t) => {{pascalCase}}Attributes;\n\nexport const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;\n";
4
4
  export declare const COMPOUND_PERSISTENCE_PARENT_INTERACTIVITY_TEMPLATE = "import { getContext, store } from '@wordpress/interactivity';\nimport { generatePublicWriteRequestId } from '@wp-typia/block-runtime/identifiers';\n\nimport { fetchBootstrap, fetchState, writeState } from './api';\nimport type {\n\t{{pascalCase}}ClientState,\n\t{{pascalCase}}Context,\n\t{{pascalCase}}State,\n} from './types';\n\nfunction hasExpiredPublicWriteToken(\n\texpiresAt?: number\n): boolean {\n\treturn (\n\t\ttypeof expiresAt === 'number' &&\n\t\texpiresAt > 0 &&\n\t\tDate.now() >= expiresAt * 1000\n\t);\n}\n\nfunction getWriteBlockedMessage(\n\tcontext: {{pascalCase}}Context\n): string {\n\treturn context.persistencePolicy === 'authenticated'\n\t\t? 'Sign in to persist this counter.'\n\t\t: 'Public writes are temporarily unavailable.';\n}\n\nconst BOOTSTRAP_MAX_ATTEMPTS = 3;\nconst BOOTSTRAP_RETRY_DELAYS_MS = [ 250, 500 ];\n\nasync function waitForBootstrapRetry( delayMs: number ): Promise< void > {\n\tawait new Promise( ( resolve ) => {\n\t\tsetTimeout( resolve, delayMs );\n\t} );\n}\n\nfunction getClientState(\n\tcontext: {{pascalCase}}Context\n): {{pascalCase}}ClientState {\n\tif ( context.client ) {\n\t\treturn context.client;\n\t}\n\n\tcontext.client = {\n\t\tbootstrapError: '',\n\t\twriteExpiry: 0,\n\t\twriteNonce: '',\n\t\twriteToken: '',\n\t};\n\n\treturn context.client;\n}\n\nfunction clearBootstrapError(\n\tcontext: {{pascalCase}}Context,\n\tclientState: {{pascalCase}}ClientState\n): void {\n\tif ( context.error === clientState.bootstrapError ) {\n\t\tcontext.error = '';\n\t}\n\tclientState.bootstrapError = '';\n}\n\nfunction setBootstrapError(\n\tcontext: {{pascalCase}}Context,\n\tclientState: {{pascalCase}}ClientState,\n\tmessage: string\n): void {\n\tclientState.bootstrapError = message;\n\tcontext.error = message;\n}\n\nconst { actions, state } = store( '{{slugKebabCase}}', {\n\tstate: {\n\t\tisHydrated: false,\n\t} as {{pascalCase}}State,\n\n\tactions: {\n\t\tasync loadState() {\n\t\t\tconst context = getContext< {{pascalCase}}Context >();\n\t\t\tif ( context.postId <= 0 || ! context.resourceKey ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcontext.isLoading = true;\n\t\t\tcontext.error = '';\n\n\t\t\ttry {\n\t\t\t\tconst result = await fetchState( {\n\t\t\t\t\tpostId: context.postId,\n\t\t\t\t\tresourceKey: context.resourceKey,\n\t\t\t\t}, {\n\t\t\t\t\ttransportTarget: 'frontend',\n\t\t\t\t} );\n\t\t\t\tif ( ! result.isValid || ! result.data ) {\n\t\t\t\t\tcontext.error = result.errors[ 0 ]?.expected ?? 'Unable to load counter';\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcontext.count = result.data.count;\n\t\t\t} catch ( error ) {\n\t\t\t\tcontext.error =\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown loading error';\n\t\t\t} finally {\n\t\t\t\tcontext.isLoading = false;\n\t\t\t}\n\t\t},\n\t\tasync loadBootstrap() {\n\t\t\tconst context = getContext< {{pascalCase}}Context >();\n\t\t\tconst clientState = getClientState( context );\n\t\t\tif ( context.postId <= 0 || ! context.resourceKey ) {\n\t\t\t\tcontext.bootstrapReady = true;\n\t\t\t\tcontext.canWrite = false;\n\t\t\t\tclientState.bootstrapError = '';\n\t\t\t\tclientState.writeExpiry = 0;\n\t\t\t\tclientState.writeNonce = '';\n\t\t\t\tclientState.writeToken = '';\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcontext.isBootstrapping = true;\n\n\t\t\tlet bootstrapSucceeded = false;\n\t\t\tlet lastBootstrapError =\n\t\t\t\t'Unable to initialize write access';\n\t\t\tconst includePublicWriteCredentials = {{isPublicPersistencePolicy}};\n\t\t\tconst includeRestNonce = {{isAuthenticatedPersistencePolicy}};\n\n\t\t\tfor ( let attempt = 1; attempt <= BOOTSTRAP_MAX_ATTEMPTS; attempt += 1 ) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await fetchBootstrap( {\n\t\t\t\t\t\tpostId: context.postId,\n\t\t\t\t\t\tresourceKey: context.resourceKey,\n\t\t\t\t\t}, {\n\t\t\t\t\t\ttransportTarget: 'frontend',\n\t\t\t\t\t} );\n\t\t\t\t\tif ( ! result.isValid || ! result.data ) {\n\t\t\t\t\t\tlastBootstrapError =\n\t\t\t\t\t\t\tresult.errors[ 0 ]?.expected ??\n\t\t\t\t\t\t\t'Unable to initialize write access';\n\t\t\t\t\t\tif ( attempt < BOOTSTRAP_MAX_ATTEMPTS ) {\n\t\t\t\t\t\t\tawait waitForBootstrapRetry(\n\t\t\t\t\t\t\t\tBOOTSTRAP_RETRY_DELAYS_MS[ attempt - 1 ] ?? 750\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tclientState.writeExpiry =\n\t\t\t\t\t\tincludePublicWriteCredentials &&\n\t\t\t\t\t\t'publicWriteExpiresAt' in result.data &&\n\t\t\t\t\t\ttypeof result.data.publicWriteExpiresAt === 'number' &&\n\t\t\t\t\t\tresult.data.publicWriteExpiresAt > 0\n\t\t\t\t\t\t\t? result.data.publicWriteExpiresAt\n\t\t\t\t\t\t\t: 0;\n\t\t\t\t\tclientState.writeToken =\n\t\t\t\t\t\tincludePublicWriteCredentials &&\n\t\t\t\t\t\t'publicWriteToken' in result.data &&\n\t\t\t\t\t\ttypeof result.data.publicWriteToken === 'string' &&\n\t\t\t\t\t\tresult.data.publicWriteToken.length > 0\n\t\t\t\t\t\t\t? result.data.publicWriteToken\n\t\t\t\t\t\t\t: '';\n\t\t\t\t\tclientState.writeNonce =\n\t\t\t\t\t\tincludeRestNonce &&\n\t\t\t\t\t\t'restNonce' in result.data &&\n\t\t\t\t\t\ttypeof result.data.restNonce === 'string' &&\n\t\t\t\t\t\tresult.data.restNonce.length > 0\n\t\t\t\t\t\t\t? result.data.restNonce\n\t\t\t\t\t\t\t: '';\n\t\t\t\t\tcontext.bootstrapReady = true;\n\t\t\t\t\tcontext.canWrite =\n\t\t\t\t\t\tresult.data.canWrite === true &&\n\t\t\t\t\t\t(\n\t\t\t\t\t\t\tcontext.persistencePolicy === 'authenticated'\n\t\t\t\t\t\t\t\t? clientState.writeNonce.length > 0\n\t\t\t\t\t\t\t\t: clientState.writeToken.length > 0 &&\n\t\t\t\t\t\t\t\t\t! hasExpiredPublicWriteToken( clientState.writeExpiry )\n\t\t\t\t\t\t);\n\t\t\t\t\tclearBootstrapError( context, clientState );\n\t\t\t\t\tbootstrapSucceeded = true;\n\t\t\t\t\tbreak;\n\t\t\t\t} catch ( error ) {\n\t\t\t\t\tlastBootstrapError =\n\t\t\t\t\t\terror instanceof Error ? error.message : 'Unknown bootstrap error';\n\t\t\t\t\tif ( attempt < BOOTSTRAP_MAX_ATTEMPTS ) {\n\t\t\t\t\t\tawait waitForBootstrapRetry(\n\t\t\t\t\t\t\tBOOTSTRAP_RETRY_DELAYS_MS[ attempt - 1 ] ?? 750\n\t\t\t\t\t\t);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( ! bootstrapSucceeded ) {\n\t\t\t\tcontext.bootstrapReady = false;\n\t\t\t\tcontext.canWrite = false;\n\t\t\t\tclientState.writeExpiry = 0;\n\t\t\t\tclientState.writeNonce = '';\n\t\t\t\tclientState.writeToken = '';\n\t\t\t\tsetBootstrapError( context, clientState, lastBootstrapError );\n\t\t\t}\n\t\t\tcontext.isBootstrapping = false;\n\t\t},\n\t\tasync increment() {\n\t\t\tconst context = getContext< {{pascalCase}}Context >();\n\t\t\tconst clientState = getClientState( context );\n\t\t\tif ( context.postId <= 0 || ! context.resourceKey ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( ! context.bootstrapReady ) {\n\t\t\t\tawait actions.loadBootstrap();\n\t\t\t}\n\t\t\tif ( ! context.bootstrapReady ) {\n\t\t\t\tcontext.error = 'Write access is still initializing.';\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (\n\t\t\t\tcontext.persistencePolicy === 'public' &&\n\t\t\t\thasExpiredPublicWriteToken( clientState.writeExpiry )\n\t\t\t) {\n\t\t\t\tawait actions.loadBootstrap();\n\t\t\t}\n\t\t\tif (\n\t\t\t\tcontext.persistencePolicy === 'public' &&\n\t\t\t\thasExpiredPublicWriteToken( clientState.writeExpiry )\n\t\t\t) {\n\t\t\t\tcontext.canWrite = false;\n\t\t\t\tcontext.error = getWriteBlockedMessage( context );\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif ( ! context.canWrite ) {\n\t\t\t\tcontext.error = getWriteBlockedMessage( context );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcontext.isSaving = true;\n\t\t\tcontext.error = '';\n\n\t\t\ttry {\n\t\t\t\tconst result = await writeState( {\n\t\t\t\t\tdelta: 1,\n\t\t\t\t\tpostId: context.postId,\n\t\t\t\t\tpublicWriteRequestId:\n\t\t\t\t\t\tcontext.persistencePolicy === 'public'\n\t\t\t\t\t\t\t? generatePublicWriteRequestId()\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\tpublicWriteToken:\n\t\t\t\t\t\tcontext.persistencePolicy === 'public' &&\n\t\t\t\t\t\tclientState.writeToken.length > 0\n\t\t\t\t\t\t\t? clientState.writeToken\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\tresourceKey: context.resourceKey,\n\t\t\t\t}, {\n\t\t\t\t\trestNonce:\n\t\t\t\t\t\tclientState.writeNonce.length > 0\n\t\t\t\t\t\t\t? clientState.writeNonce\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\ttransportTarget: 'frontend',\n\t\t\t\t} );\n\t\t\t\tif ( ! result.isValid || ! result.data ) {\n\t\t\t\t\tcontext.error = result.errors[ 0 ]?.expected ?? 'Unable to update counter';\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcontext.count = result.data.count;\n\t\t\t\tcontext.storage = result.data.storage;\n\t\t\t} catch ( error ) {\n\t\t\t\tcontext.error =\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown update error';\n\t\t\t} finally {\n\t\t\t\tcontext.isSaving = false;\n\t\t\t}\n\t\t},\n\t},\n\n\tcallbacks: {\n\t\tinit() {\n\t\t\tconst context = getContext< {{pascalCase}}Context >();\n\t\t\tcontext.client = {\n\t\t\t\tbootstrapError: '',\n\t\t\t\twriteExpiry: 0,\n\t\t\t\twriteNonce: '',\n\t\t\t\twriteToken: '',\n\t\t\t};\n\t\t\tcontext.bootstrapReady = false;\n\t\t\tcontext.canWrite = false;\n\t\t\tcontext.count = 0;\n\t\t\tcontext.error = '';\n\t\t\tcontext.isBootstrapping = false;\n\t\t\tcontext.isLoading = false;\n\t\t\tcontext.isSaving = false;\n\t\t},\n\t\tmounted() {\n\t\t\tstate.isHydrated = true;\n\t\t\tif ( typeof document !== 'undefined' ) {\n\t\t\t\tdocument.documentElement.dataset[ '{{slugCamelCase}}Hydrated' ] = 'true';\n\t\t\t}\n\t\t\tvoid Promise.allSettled( [\n\t\t\t\tactions.loadState(),\n\t\t\t\tactions.loadBootstrap(),\n\t\t\t] );\n\t\t},\n\t},\n} );\n";