@wp-typia/project-tools 0.16.9 → 0.16.11

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.
@@ -116,6 +116,36 @@ function defineNumberAttribute(spec) {
116
116
  sourceType: "number",
117
117
  });
118
118
  }
119
+ function resolveBuiltInAttributeValue(value, context) {
120
+ if (typeof value === "function") {
121
+ return value(context);
122
+ }
123
+ return value;
124
+ }
125
+ function buildAttributesFromSpecs(specs, context) {
126
+ return specs.map((spec) => {
127
+ const resolvedSpec = {
128
+ blockJsonDefaultValue: resolveBuiltInAttributeValue(spec.blockJsonDefaultValue, context),
129
+ constraints: resolveBuiltInAttributeValue(spec.constraints, context),
130
+ defaultValue: resolveBuiltInAttributeValue(spec.defaultValue, context),
131
+ description: resolveBuiltInAttributeValue(spec.description, context),
132
+ enumValues: resolveBuiltInAttributeValue(spec.enumValues, context),
133
+ manifestDefaultValue: resolveBuiltInAttributeValue(spec.manifestDefaultValue, context),
134
+ name: spec.name,
135
+ optional: spec.optional,
136
+ selector: resolveBuiltInAttributeValue(spec.selector, context),
137
+ source: resolveBuiltInAttributeValue(spec.source, context),
138
+ typeExpression: resolveBuiltInAttributeValue(spec.typeExpression, context) ?? "unknown",
139
+ };
140
+ if (spec.attributeType === "boolean") {
141
+ return defineBooleanAttribute(resolvedSpec);
142
+ }
143
+ if (spec.attributeType === "number") {
144
+ return defineNumberAttribute(resolvedSpec);
145
+ }
146
+ return defineStringAttribute(resolvedSpec);
147
+ });
148
+ }
119
149
  function buildManifestDocument(sourceType, attributes) {
120
150
  return {
121
151
  attributes: Object.fromEntries(attributes.map((attribute) => [
@@ -175,289 +205,327 @@ function emitTypesModule({ preambleLines, interfaces, typeAliases, }) {
175
205
  function stringifyBlockJsonDocument(document) {
176
206
  return `${JSON.stringify(document, null, "\t")}\n`;
177
207
  }
208
+ const BASIC_ATTRIBUTE_SPECS = [
209
+ {
210
+ attributeType: "string",
211
+ constraints: {
212
+ maxLength: 1000,
213
+ },
214
+ defaultValue: "",
215
+ description: describe("Main block content"),
216
+ name: "content",
217
+ optional: false,
218
+ typeExpression: 'string & tags.MaxLength<1000> & tags.Default<"">',
219
+ },
220
+ {
221
+ attributeType: "string",
222
+ defaultValue: "left",
223
+ description: describe("Alignment"),
224
+ enumValues: [...BASIC_ALIGNMENT_VALUES],
225
+ name: "alignment",
226
+ optional: true,
227
+ typeExpression: 'TextAlignment & tags.Default<"left">',
228
+ },
229
+ {
230
+ attributeType: "boolean",
231
+ defaultValue: true,
232
+ description: describe("Visibility toggle"),
233
+ name: "isVisible",
234
+ optional: true,
235
+ typeExpression: "boolean & tags.Default<true>",
236
+ },
237
+ {
238
+ attributeType: "string",
239
+ constraints: {
240
+ maxLength: 100,
241
+ },
242
+ defaultValue: "",
243
+ description: describe("Custom CSS class"),
244
+ name: "className",
245
+ optional: true,
246
+ typeExpression: 'string & tags.MaxLength<100> & tags.Default<"">',
247
+ },
248
+ {
249
+ attributeType: "string",
250
+ constraints: {
251
+ format: "uuid",
252
+ },
253
+ description: describe("Generated runtime ID"),
254
+ name: "id",
255
+ optional: true,
256
+ typeExpression: 'string & tags.Format<"uuid">',
257
+ },
258
+ {
259
+ attributeType: "number",
260
+ constraints: {
261
+ typeTag: "uint32",
262
+ },
263
+ defaultValue: 1,
264
+ description: describe("Block version for migrations"),
265
+ name: "schemaVersion",
266
+ optional: true,
267
+ typeExpression: 'number & tags.Type<"uint32"> & tags.Default<1>',
268
+ },
269
+ ];
270
+ const INTERACTIVITY_ATTRIBUTE_SPECS = [
271
+ {
272
+ attributeType: "string",
273
+ constraints: {
274
+ maxLength: 1000,
275
+ },
276
+ defaultValue: "",
277
+ name: "content",
278
+ optional: false,
279
+ selector: (variables) => `.${variables.cssClassName}__content`,
280
+ source: "html",
281
+ typeExpression: 'string & tags.MaxLength<1000> & tags.Default<"">',
282
+ },
283
+ {
284
+ attributeType: "string",
285
+ defaultValue: "left",
286
+ enumValues: [...ALIGNMENT_VALUES],
287
+ name: "alignment",
288
+ optional: true,
289
+ typeExpression: 'TextAlignment & tags.Default<"left">',
290
+ },
291
+ {
292
+ attributeType: "boolean",
293
+ defaultValue: true,
294
+ name: "isVisible",
295
+ optional: true,
296
+ typeExpression: "boolean & tags.Default<true>",
297
+ },
298
+ {
299
+ attributeType: "string",
300
+ defaultValue: "click",
301
+ enumValues: [...INTERACTIVE_MODE_VALUES],
302
+ name: "interactiveMode",
303
+ optional: true,
304
+ typeExpression: '("click" | "hover") & tags.Default<"click">',
305
+ },
306
+ {
307
+ attributeType: "string",
308
+ defaultValue: "none",
309
+ enumValues: [...ANIMATION_VALUES],
310
+ name: "animation",
311
+ optional: true,
312
+ typeExpression: '("none" | "bounce" | "pulse" | "shake" | "flip") & tags.Default<"none">',
313
+ },
314
+ {
315
+ attributeType: "number",
316
+ constraints: {
317
+ minimum: 0,
318
+ typeTag: "uint32",
319
+ },
320
+ defaultValue: 0,
321
+ name: "clickCount",
322
+ optional: true,
323
+ typeExpression: 'number & tags.Minimum<0> & tags.Type<"uint32"> & tags.Default<0>',
324
+ },
325
+ {
326
+ attributeType: "boolean",
327
+ defaultValue: false,
328
+ name: "isAnimating",
329
+ optional: true,
330
+ typeExpression: "boolean & tags.Default<false>",
331
+ },
332
+ {
333
+ attributeType: "boolean",
334
+ defaultValue: true,
335
+ name: "showCounter",
336
+ optional: true,
337
+ typeExpression: "boolean & tags.Default<true>",
338
+ },
339
+ {
340
+ attributeType: "number",
341
+ constraints: {
342
+ minimum: 0,
343
+ typeTag: "uint32",
344
+ },
345
+ defaultValue: 10,
346
+ name: "maxClicks",
347
+ optional: true,
348
+ typeExpression: 'number & tags.Minimum<0> & tags.Type<"uint32"> & tags.Default<10>',
349
+ },
350
+ ];
351
+ const PERSISTENCE_ATTRIBUTE_SPECS = [
352
+ {
353
+ attributeType: "string",
354
+ constraints: {
355
+ maxLength: 250,
356
+ minLength: 1,
357
+ },
358
+ defaultValue: (variables) => `${variables.title} persistence block`,
359
+ name: "content",
360
+ optional: false,
361
+ selector: (variables) => `.${variables.cssClassName}__content`,
362
+ source: "html",
363
+ typeExpression: (variables) => `string & tags.MinLength<1> & tags.MaxLength<250> & tags.Default<${quote(`${variables.title} persistence block`)}>`,
364
+ },
365
+ {
366
+ attributeType: "string",
367
+ defaultValue: "left",
368
+ enumValues: [...ALIGNMENT_VALUES],
369
+ name: "alignment",
370
+ optional: true,
371
+ typeExpression: 'TextAlignment & tags.Default<"left">',
372
+ },
373
+ {
374
+ attributeType: "boolean",
375
+ defaultValue: true,
376
+ name: "isVisible",
377
+ optional: true,
378
+ typeExpression: "boolean & tags.Default<true>",
379
+ },
380
+ {
381
+ attributeType: "boolean",
382
+ defaultValue: true,
383
+ name: "showCount",
384
+ optional: true,
385
+ typeExpression: "boolean & tags.Default<true>",
386
+ },
387
+ {
388
+ attributeType: "string",
389
+ constraints: {
390
+ maxLength: 40,
391
+ minLength: 1,
392
+ },
393
+ defaultValue: "Persist Count",
394
+ name: "buttonLabel",
395
+ optional: true,
396
+ typeExpression: 'string & tags.MinLength<1> & tags.MaxLength<40> & tags.Default<"Persist Count">',
397
+ },
398
+ {
399
+ attributeType: "string",
400
+ blockJsonDefaultValue: "",
401
+ constraints: {
402
+ maxLength: 100,
403
+ minLength: 1,
404
+ },
405
+ manifestDefaultValue: "primary",
406
+ name: "resourceKey",
407
+ optional: true,
408
+ typeExpression: 'string & tags.MinLength<1> & tags.MaxLength<100> & tags.Default<"primary">',
409
+ },
410
+ ];
411
+ const COMPOUND_PARENT_BASE_ATTRIBUTE_SPECS = [
412
+ {
413
+ attributeType: "string",
414
+ constraints: {
415
+ maxLength: 80,
416
+ minLength: 1,
417
+ },
418
+ defaultValue: (variables) => variables.title,
419
+ name: "heading",
420
+ optional: false,
421
+ selector: (variables) => `.${variables.cssClassName}__heading`,
422
+ source: "html",
423
+ typeExpression: (variables) => `string & tags.MinLength<1> & tags.MaxLength<80> & tags.Default<${quote(variables.title)}>`,
424
+ },
425
+ {
426
+ attributeType: "string",
427
+ constraints: {
428
+ maxLength: 180,
429
+ minLength: 1,
430
+ },
431
+ defaultValue: "Add and reorder internal items inside this compound block.",
432
+ name: "intro",
433
+ optional: true,
434
+ selector: (variables) => `.${variables.cssClassName}__intro`,
435
+ source: "html",
436
+ typeExpression: 'string & tags.MinLength<1> & tags.MaxLength<180> & tags.Default<"Add and reorder internal items inside this compound block.">',
437
+ },
438
+ {
439
+ attributeType: "boolean",
440
+ defaultValue: true,
441
+ name: "showDividers",
442
+ optional: true,
443
+ typeExpression: "boolean & tags.Default<true>",
444
+ },
445
+ ];
446
+ const COMPOUND_PARENT_PERSISTENCE_ATTRIBUTE_SPECS = [
447
+ {
448
+ attributeType: "boolean",
449
+ defaultValue: true,
450
+ name: "showCount",
451
+ optional: true,
452
+ typeExpression: "boolean & tags.Default<true>",
453
+ },
454
+ {
455
+ attributeType: "string",
456
+ constraints: {
457
+ maxLength: 40,
458
+ minLength: 1,
459
+ },
460
+ defaultValue: "Persist Count",
461
+ name: "buttonLabel",
462
+ optional: true,
463
+ typeExpression: 'string & tags.MinLength<1> & tags.MaxLength<40> & tags.Default<"Persist Count">',
464
+ },
465
+ {
466
+ attributeType: "string",
467
+ blockJsonDefaultValue: "",
468
+ constraints: {
469
+ maxLength: 100,
470
+ minLength: 1,
471
+ },
472
+ manifestDefaultValue: "primary",
473
+ name: "resourceKey",
474
+ optional: true,
475
+ typeExpression: 'string & tags.MinLength<1> & tags.MaxLength<100> & tags.Default<"primary">',
476
+ },
477
+ ];
478
+ const COMPOUND_CHILD_ATTRIBUTE_SPECS = [
479
+ {
480
+ attributeType: "string",
481
+ constraints: {
482
+ maxLength: 80,
483
+ minLength: 1,
484
+ },
485
+ defaultValue: ({ childTitle }) => childTitle,
486
+ name: "title",
487
+ optional: false,
488
+ selector: ({ childCssClassName }) => childCssClassName ? `.${childCssClassName}__title` : null,
489
+ source: ({ childCssClassName }) => childCssClassName ? "html" : null,
490
+ typeExpression: ({ childTitle }) => `string & tags.MinLength<1> & tags.MaxLength<80> & tags.Default<${quote(childTitle)}>`,
491
+ },
492
+ {
493
+ attributeType: "string",
494
+ constraints: {
495
+ maxLength: 280,
496
+ minLength: 1,
497
+ },
498
+ defaultValue: ({ bodyPlaceholder }) => bodyPlaceholder,
499
+ name: "body",
500
+ optional: false,
501
+ selector: ({ childCssClassName }) => childCssClassName ? `.${childCssClassName}__body` : null,
502
+ source: ({ childCssClassName }) => childCssClassName ? "html" : null,
503
+ typeExpression: ({ bodyPlaceholder }) => `string & tags.MinLength<1> & tags.MaxLength<280> & tags.Default<${quote(bodyPlaceholder)}>`,
504
+ },
505
+ ];
178
506
  function buildBasicAttributes() {
179
- return [
180
- defineStringAttribute({
181
- constraints: {
182
- maxLength: 1000,
183
- },
184
- defaultValue: "",
185
- description: describe("Main block content"),
186
- name: "content",
187
- optional: false,
188
- typeExpression: 'string & tags.MaxLength<1000> & tags.Default<"">',
189
- }),
190
- defineStringAttribute({
191
- defaultValue: "left",
192
- description: describe("Alignment"),
193
- enumValues: [...BASIC_ALIGNMENT_VALUES],
194
- name: "alignment",
195
- optional: true,
196
- typeExpression: 'TextAlignment & tags.Default<"left">',
197
- }),
198
- defineBooleanAttribute({
199
- defaultValue: true,
200
- description: describe("Visibility toggle"),
201
- name: "isVisible",
202
- optional: true,
203
- typeExpression: "boolean & tags.Default<true>",
204
- }),
205
- defineStringAttribute({
206
- constraints: {
207
- maxLength: 100,
208
- },
209
- defaultValue: "",
210
- description: describe("Custom CSS class"),
211
- name: "className",
212
- optional: true,
213
- typeExpression: 'string & tags.MaxLength<100> & tags.Default<"">',
214
- }),
215
- defineStringAttribute({
216
- constraints: {
217
- format: "uuid",
218
- },
219
- description: describe("Generated runtime ID"),
220
- name: "id",
221
- optional: true,
222
- typeExpression: 'string & tags.Format<"uuid">',
223
- }),
224
- defineNumberAttribute({
225
- constraints: {
226
- typeTag: "uint32",
227
- },
228
- defaultValue: 1,
229
- description: describe("Block version for migrations"),
230
- name: "schemaVersion",
231
- optional: true,
232
- typeExpression: 'number & tags.Type<"uint32"> & tags.Default<1>',
233
- }),
234
- ];
507
+ return buildAttributesFromSpecs(BASIC_ATTRIBUTE_SPECS, undefined);
235
508
  }
236
509
  function buildInteractivityAttributes(variables) {
237
- return [
238
- defineStringAttribute({
239
- constraints: {
240
- maxLength: 1000,
241
- },
242
- defaultValue: "",
243
- name: "content",
244
- optional: false,
245
- selector: `.${variables.cssClassName}__content`,
246
- source: "html",
247
- typeExpression: 'string & tags.MaxLength<1000> & tags.Default<"">',
248
- }),
249
- defineStringAttribute({
250
- defaultValue: "left",
251
- enumValues: [...ALIGNMENT_VALUES],
252
- name: "alignment",
253
- optional: true,
254
- typeExpression: 'TextAlignment & tags.Default<"left">',
255
- }),
256
- defineBooleanAttribute({
257
- defaultValue: true,
258
- name: "isVisible",
259
- optional: true,
260
- typeExpression: "boolean & tags.Default<true>",
261
- }),
262
- defineStringAttribute({
263
- defaultValue: "click",
264
- enumValues: [...INTERACTIVE_MODE_VALUES],
265
- name: "interactiveMode",
266
- optional: true,
267
- typeExpression: '("click" | "hover") & tags.Default<"click">',
268
- }),
269
- defineStringAttribute({
270
- defaultValue: "none",
271
- enumValues: [...ANIMATION_VALUES],
272
- name: "animation",
273
- optional: true,
274
- typeExpression: '("none" | "bounce" | "pulse" | "shake" | "flip") & tags.Default<"none">',
275
- }),
276
- defineNumberAttribute({
277
- constraints: {
278
- minimum: 0,
279
- typeTag: "uint32",
280
- },
281
- defaultValue: 0,
282
- name: "clickCount",
283
- optional: true,
284
- typeExpression: 'number & tags.Minimum<0> & tags.Type<"uint32"> & tags.Default<0>',
285
- }),
286
- defineBooleanAttribute({
287
- defaultValue: false,
288
- name: "isAnimating",
289
- optional: true,
290
- typeExpression: "boolean & tags.Default<false>",
291
- }),
292
- defineBooleanAttribute({
293
- defaultValue: true,
294
- name: "showCounter",
295
- optional: true,
296
- typeExpression: "boolean & tags.Default<true>",
297
- }),
298
- defineNumberAttribute({
299
- constraints: {
300
- minimum: 0,
301
- typeTag: "uint32",
302
- },
303
- defaultValue: 10,
304
- name: "maxClicks",
305
- optional: true,
306
- typeExpression: 'number & tags.Minimum<0> & tags.Type<"uint32"> & tags.Default<10>',
307
- }),
308
- ];
510
+ return buildAttributesFromSpecs(INTERACTIVITY_ATTRIBUTE_SPECS, variables);
309
511
  }
310
512
  function buildPersistenceAttributes(variables) {
311
- return [
312
- defineStringAttribute({
313
- constraints: {
314
- maxLength: 250,
315
- minLength: 1,
316
- },
317
- defaultValue: `${variables.title} persistence block`,
318
- name: "content",
319
- optional: false,
320
- selector: `.${variables.cssClassName}__content`,
321
- source: "html",
322
- typeExpression: `string & tags.MinLength<1> & tags.MaxLength<250> & tags.Default<${quote(`${variables.title} persistence block`)}>`,
323
- }),
324
- defineStringAttribute({
325
- defaultValue: "left",
326
- enumValues: [...ALIGNMENT_VALUES],
327
- name: "alignment",
328
- optional: true,
329
- typeExpression: 'TextAlignment & tags.Default<"left">',
330
- }),
331
- defineBooleanAttribute({
332
- defaultValue: true,
333
- name: "isVisible",
334
- optional: true,
335
- typeExpression: "boolean & tags.Default<true>",
336
- }),
337
- defineBooleanAttribute({
338
- defaultValue: true,
339
- name: "showCount",
340
- optional: true,
341
- typeExpression: "boolean & tags.Default<true>",
342
- }),
343
- defineStringAttribute({
344
- constraints: {
345
- maxLength: 40,
346
- minLength: 1,
347
- },
348
- defaultValue: "Persist Count",
349
- name: "buttonLabel",
350
- optional: true,
351
- typeExpression: 'string & tags.MinLength<1> & tags.MaxLength<40> & tags.Default<"Persist Count">',
352
- }),
353
- defineStringAttribute({
354
- blockJsonDefaultValue: "",
355
- constraints: {
356
- maxLength: 100,
357
- minLength: 1,
358
- },
359
- manifestDefaultValue: "primary",
360
- name: "resourceKey",
361
- optional: true,
362
- typeExpression: 'string & tags.MinLength<1> & tags.MaxLength<100> & tags.Default<"primary">',
363
- }),
364
- ];
513
+ return buildAttributesFromSpecs(PERSISTENCE_ATTRIBUTE_SPECS, variables);
365
514
  }
366
515
  function buildCompoundParentAttributes(variables) {
367
- const attributes = [
368
- defineStringAttribute({
369
- constraints: {
370
- maxLength: 80,
371
- minLength: 1,
372
- },
373
- defaultValue: variables.title,
374
- name: "heading",
375
- optional: false,
376
- selector: `.${variables.cssClassName}__heading`,
377
- source: "html",
378
- typeExpression: `string & tags.MinLength<1> & tags.MaxLength<80> & tags.Default<${quote(variables.title)}>`,
379
- }),
380
- defineStringAttribute({
381
- constraints: {
382
- maxLength: 180,
383
- minLength: 1,
384
- },
385
- defaultValue: "Add and reorder internal items inside this compound block.",
386
- name: "intro",
387
- optional: true,
388
- selector: `.${variables.cssClassName}__intro`,
389
- source: "html",
390
- typeExpression: 'string & tags.MinLength<1> & tags.MaxLength<180> & tags.Default<"Add and reorder internal items inside this compound block.">',
391
- }),
392
- defineBooleanAttribute({
393
- defaultValue: true,
394
- name: "showDividers",
395
- optional: true,
396
- typeExpression: "boolean & tags.Default<true>",
397
- }),
398
- ];
399
- if (variables.compoundPersistenceEnabled === "true") {
400
- attributes.push(defineBooleanAttribute({
401
- defaultValue: true,
402
- name: "showCount",
403
- optional: true,
404
- typeExpression: "boolean & tags.Default<true>",
405
- }), defineStringAttribute({
406
- constraints: {
407
- maxLength: 40,
408
- minLength: 1,
409
- },
410
- defaultValue: "Persist Count",
411
- name: "buttonLabel",
412
- optional: true,
413
- typeExpression: 'string & tags.MinLength<1> & tags.MaxLength<40> & tags.Default<"Persist Count">',
414
- }), defineStringAttribute({
415
- blockJsonDefaultValue: "",
416
- constraints: {
417
- maxLength: 100,
418
- minLength: 1,
419
- },
420
- manifestDefaultValue: "primary",
421
- name: "resourceKey",
422
- optional: true,
423
- typeExpression: 'string & tags.MinLength<1> & tags.MaxLength<100> & tags.Default<"primary">',
424
- }));
425
- }
426
- return attributes;
516
+ return buildAttributesFromSpecs(variables.compoundPersistenceEnabled === "true"
517
+ ? [
518
+ ...COMPOUND_PARENT_BASE_ATTRIBUTE_SPECS,
519
+ ...COMPOUND_PARENT_PERSISTENCE_ATTRIBUTE_SPECS,
520
+ ]
521
+ : COMPOUND_PARENT_BASE_ATTRIBUTE_SPECS, variables);
427
522
  }
428
523
  function buildCompoundChildAttributes(bodyPlaceholder = DEFAULT_COMPOUND_CHILD_BODY_PLACEHOLDER, childTitle, childCssClassName) {
429
- const titleSelector = childCssClassName
430
- ? `.${childCssClassName}__title`
431
- : null;
432
- const bodySelector = childCssClassName
433
- ? `.${childCssClassName}__body`
434
- : null;
435
- return [
436
- defineStringAttribute({
437
- constraints: {
438
- maxLength: 80,
439
- minLength: 1,
440
- },
441
- defaultValue: childTitle,
442
- name: "title",
443
- optional: false,
444
- selector: titleSelector,
445
- source: titleSelector ? "html" : null,
446
- typeExpression: `string & tags.MinLength<1> & tags.MaxLength<80> & tags.Default<${quote(childTitle)}>`,
447
- }),
448
- defineStringAttribute({
449
- constraints: {
450
- maxLength: 280,
451
- minLength: 1,
452
- },
453
- defaultValue: bodyPlaceholder,
454
- name: "body",
455
- optional: false,
456
- selector: bodySelector,
457
- source: bodySelector ? "html" : null,
458
- typeExpression: `string & tags.MinLength<1> & tags.MaxLength<280> & tags.Default<${quote(bodyPlaceholder)}>`,
459
- }),
460
- ];
524
+ return buildAttributesFromSpecs(COMPOUND_CHILD_ATTRIBUTE_SPECS, {
525
+ bodyPlaceholder,
526
+ childCssClassName,
527
+ childTitle,
528
+ });
461
529
  }
462
530
  function buildBasicTypesSource(variables, attributes) {
463
531
  return emitTypesModule({
@@ -8,27 +8,27 @@ export declare const MANIFEST_DOCUMENT_WRAPPER_TEMPLATE = "import rawCurrentMani
8
8
  export declare const MANIFEST_DEFAULTS_DOCUMENT_WRAPPER_TEMPLATE = "import rawCurrentManifest from './typia.manifest.json';\nimport { defineManifestDefaultsDocument } from '@wp-typia/block-runtime/defaults';\n\nconst currentManifest = defineManifestDefaultsDocument(rawCurrentManifest);\n\nexport default currentManifest;\n";
9
9
  export declare const BASIC_EDIT_TEMPLATE = "/**\n * Editor component for {{title}} Block\n */\n\nimport type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';\nimport {\n InspectorControls,\n RichText,\n useBlockProps,\n} from '@wordpress/block-editor';\nimport { Notice, PanelBody, TextControl } from '@wordpress/components';\nimport { __ } from '@wordpress/i18n';\nimport currentManifest from './manifest-document';\nimport {\n InspectorFromManifest,\n useEditorFields,\n useTypedAttributeUpdater,\n} from '@wp-typia/block-runtime/inspector';\nimport { {{pascalCase}}Attributes } from './types';\nimport {\n sanitize{{pascalCase}}Attributes,\n validate{{pascalCase}}Attributes,\n} from './validators';\nimport { useTypiaValidation } from './hooks';\n\ntype EditProps = BlockEditProps<{{pascalCase}}Attributes>;\n\nconst validationErrorItemStyle = { color: '#cc1818', fontSize: '12px' };\nconst validationListStyle = { margin: 0, paddingLeft: '1em' };\n\nfunction Edit({ attributes, setAttributes }: EditProps) {\n const isVisible = attributes.isVisible !== false;\n const blockProps = useBlockProps({\n className: `{{cssClassName}}${isVisible ? '' : ' is-hidden'}`,\n });\n const editorFields = useEditorFields(currentManifest, {\n hidden: ['id', 'schemaVersion'],\n manual: ['content'],\n labels: {\n alignment: __('Alignment', '{{textDomain}}'),\n className: __('CSS Class', '{{textDomain}}'),\n content: __('Content', '{{textDomain}}'),\n isVisible: __('Visible', '{{textDomain}}'),\n },\n });\n const classNameField = editorFields.getField('className');\n const { errorMessages, isValid } = useTypiaValidation(\n attributes,\n validate{{pascalCase}}Attributes\n );\n const validateEditorUpdate = (nextAttributes: {{pascalCase}}Attributes) => {\n try {\n return {\n data: sanitize{{pascalCase}}Attributes(nextAttributes),\n errors: [],\n isValid: true as const,\n };\n } catch {\n return validate{{pascalCase}}Attributes(nextAttributes);\n }\n };\n const { updateField } = useTypedAttributeUpdater(\n attributes,\n setAttributes,\n validateEditorUpdate\n );\n\n return (\n <>\n <InspectorControls>\n <InspectorFromManifest\n attributes={attributes}\n fieldLookup={editorFields}\n onChange={updateField}\n paths={['alignment', 'isVisible']}\n title={__('Settings', '{{textDomain}}')}\n >\n <TextControl\n label={__('Content', '{{textDomain}}')}\n value={attributes.content || ''}\n onChange={(value) => updateField('content', value)}\n help={__('Mirrors the main block content.', '{{textDomain}}')}\n />\n\n <TextControl\n label={classNameField?.label || __('CSS Class', '{{textDomain}}')}\n value={attributes.className || ''}\n onChange={(value) => updateField('className', value)}\n help={__('Add an optional CSS class name.', '{{textDomain}}')}\n />\n </InspectorFromManifest>\n\n {!isValid && (\n <PanelBody title={__('Validation Errors', '{{textDomain}}')} initialOpen>\n {errorMessages.map((error, index) => (\n <div key={index} style={validationErrorItemStyle}>\n \u2022 {error}\n </div>\n ))}\n </PanelBody>\n )}\n </InspectorControls>\n\n <div {...blockProps}>\n <div className=\"{{cssClassName}}__content\">\n <RichText\n tagName=\"p\"\n value={attributes.content || ''}\n onChange={(value) => updateField('content', value)}\n placeholder={__('Add your content...', '{{textDomain}}')}\n />\n </div>\n {!isValid && (\n <Notice status=\"error\" isDismissible={false}>\n <p>\n <strong>{__('Validation Errors', '{{textDomain}}')}</strong>\n </p>\n <ul style={validationListStyle}>\n {errorMessages.map((error, index) => (\n <li key={index}>{error}</li>\n ))}\n </ul>\n </Notice>\n )}\n </div>\n </>\n );\n}\n\nexport default Edit;\n";
10
10
  export declare const BASIC_SAVE_TEMPLATE = "/**\n * Save/Frontend component for {{title}} Block\n */\n\nimport { RichText, useBlockProps } from '@wordpress/block-editor';\nimport { {{pascalCase}}Attributes } from './types';\n\ninterface SaveProps {\n attributes: {{pascalCase}}Attributes;\n}\n\nexport default function Save({ attributes }: SaveProps) {\n const isVisible = attributes.isVisible !== false;\n const blockProps = useBlockProps.save({\n className: `{{cssClassName}}${isVisible ? '' : ' is-hidden'}`,\n hidden: isVisible ? undefined : true,\n 'aria-hidden': isVisible ? undefined : 'true',\n });\n\n return (\n <div {...blockProps}>\n <div\n className=\"{{cssClassName}}__content\"\n data-align={attributes.alignment || 'left'}\n >\n <RichText.Content tagName=\"p\" value={attributes.content} />\n </div>\n </div>\n );\n}\n";
11
- export declare const BASIC_INDEX_TEMPLATE = "/**\n * WordPress {{title}} Block\n *\n * Typia-powered type-safe block\n */\n\nimport { registerBlockType } from '@wordpress/blocks';\nimport type { BlockConfiguration } from '@wp-typia/block-types/blocks/registration';\nimport type { BlockSupports } from '@wp-typia/block-types/blocks/supports';\nimport { __ } from '@wordpress/i18n';\nimport {\n buildScaffoldBlockRegistration,\n parseScaffoldBlockMetadata,\n} from '@wp-typia/block-runtime/blocks';\n\n// Import components\nimport Edit from './edit';\nimport Save from './save';\nimport metadata from './block-metadata';\nimport './editor.scss';\nimport './style.scss';\n\n// Import types\nimport { {{pascalCase}}Attributes } from './types';\nimport { validators } from './validators';\n\nconst scaffoldSupports = {\n html: false,\n multiple: true,\n align: ['wide', 'full'],\n} satisfies BlockSupports;\n\n// Register the block\nconst registration = buildScaffoldBlockRegistration(\n parseScaffoldBlockMetadata<BlockConfiguration<{{pascalCase}}Attributes>>(metadata),\n {\n supports: scaffoldSupports,\n example: {\n attributes: validators.random(),\n },\n edit: Edit,\n save: Save,\n }\n);\n\nregisterBlockType<{{pascalCase}}Attributes>(registration.name, registration.settings);\n";
11
+ export declare const BASIC_INDEX_TEMPLATE = "/**\n * WordPress {{title}} Block\n *\n * Typia-powered type-safe block\n */\n\nimport {\n registerScaffoldBlockType,\n type BlockConfiguration,\n} from '@wp-typia/block-types/blocks/registration';\nimport type { BlockSupports } from '@wp-typia/block-types/blocks/supports';\nimport { __ } from '@wordpress/i18n';\nimport {\n buildScaffoldBlockRegistration,\n parseScaffoldBlockMetadata,\n} from '@wp-typia/block-runtime/blocks';\n\n// Import components\nimport Edit from './edit';\nimport Save from './save';\nimport metadata from './block-metadata';\nimport './editor.scss';\nimport './style.scss';\n\n// Import types\nimport { {{pascalCase}}Attributes } from './types';\nimport { validators } from './validators';\n\nconst scaffoldSupports = {\n html: false,\n multiple: true,\n align: ['wide', 'full'],\n} satisfies BlockSupports;\n\n// Register the block\nconst registration = buildScaffoldBlockRegistration(\n parseScaffoldBlockMetadata<BlockConfiguration<{{pascalCase}}Attributes>>(metadata),\n {\n supports: scaffoldSupports,\n example: {\n attributes: validators.random(),\n },\n edit: Edit,\n save: Save,\n }\n);\n\nregisterScaffoldBlockType(registration.name, registration.settings);\n";
12
12
  export declare const BASIC_VALIDATORS_TEMPLATE = "import typia from 'typia';\nimport currentManifest from \"./manifest-defaults-document\";\nimport { {{pascalCase}}Attributes, {{pascalCase}}ValidationResult } from \"./types\";\nimport { generateBlockId } from \"@wp-typia/block-runtime/identifiers\";\nimport { createTemplateValidatorToolkit } from \"./validator-toolkit\";\n\nconst scaffoldValidators = createTemplateValidatorToolkit<{{pascalCase}}Attributes>({\n assert: typia.createAssert<{{pascalCase}}Attributes>(),\n clone: typia.misc.createClone<{{pascalCase}}Attributes>() as (\n value: {{pascalCase}}Attributes,\n ) => {{pascalCase}}Attributes,\n is: typia.createIs<{{pascalCase}}Attributes>(),\n manifest: currentManifest,\n prune: typia.misc.createPrune<{{pascalCase}}Attributes>(),\n random: typia.createRandom<{{pascalCase}}Attributes>() as (\n ...args: unknown[]\n ) => {{pascalCase}}Attributes,\n finalize: (normalized) => ({\n ...normalized,\n id: normalized.id && normalized.id.length > 0 ? normalized.id : generateBlockId(),\n }),\n validate: typia.createValidate<{{pascalCase}}Attributes>(),\n});\n\nexport const validate{{pascalCase}}Attributes =\n scaffoldValidators.validateAttributes as (\n attributes: unknown,\n ) => {{pascalCase}}ValidationResult;\n\nexport const validators = scaffoldValidators.validators;\n\nexport const sanitize{{pascalCase}}Attributes =\n scaffoldValidators.sanitizeAttributes as (\n attributes: Partial<{{pascalCase}}Attributes>,\n ) => {{pascalCase}}Attributes;\n\nexport const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;\n";
13
13
  export declare const INTERACTIVITY_EDIT_TEMPLATE = "import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';\nimport { __ } from '@wordpress/i18n';\nimport { useBlockProps, InspectorControls, RichText, BlockControls, AlignmentToolbar } from '@wordpress/block-editor';\nimport { PanelBody, RangeControl, Button, Notice } from '@wordpress/components';\nimport { useState } from '@wordpress/element';\nimport currentManifest from './manifest-document';\nimport {\n InspectorFromManifest,\n useEditorFields,\n useTypedAttributeUpdater,\n} from '@wp-typia/block-runtime/inspector';\nimport type { {{pascalCase}}Attributes } from './types';\nimport {\n sanitize{{pascalCase}}Attributes,\n validate{{pascalCase}}Attributes,\n} from './validators';\nimport { useTypiaValidation } from './hooks';\n\ntype EditProps = BlockEditProps<{{pascalCase}}Attributes>;\n\nconst actionButtonRowStyle = { display: 'flex', gap: '8px', marginTop: '16px' };\nconst validationListStyle = { margin: 0, paddingLeft: '1em' };\n\nexport default function Edit({ attributes, setAttributes, isSelected }: EditProps) {\n const [isPreviewing, setIsPreviewing] = useState(false);\n const editorFields = useEditorFields(currentManifest, {\n manual: ['content', 'clickCount', 'maxClicks'],\n labels: {\n alignment: __('Alignment', '{{textDomain}}'),\n animation: __('Animation', '{{textDomain}}'),\n interactiveMode: __('Interactive Mode', '{{textDomain}}'),\n isVisible: __('Visible', '{{textDomain}}'),\n showCounter: __('Show Counter', '{{textDomain}}'),\n },\n });\n const { errorMessages, isValid } = useTypiaValidation(\n attributes,\n validate{{pascalCase}}Attributes,\n );\n const validateEditorUpdate = (nextAttributes: {{pascalCase}}Attributes) => {\n try {\n return {\n data: sanitize{{pascalCase}}Attributes(nextAttributes),\n errors: [],\n isValid: true as const,\n };\n } catch {\n return validate{{pascalCase}}Attributes(nextAttributes);\n }\n };\n const { updateField } = useTypedAttributeUpdater(\n attributes,\n setAttributes,\n validateEditorUpdate\n );\n const alignmentValue = editorFields.getStringValue(\n attributes,\n 'alignment',\n 'left'\n ) as NonNullable<{{pascalCase}}Attributes['alignment']>;\n const clickCount = attributes.clickCount ?? 0;\n const isVisible = editorFields.getBooleanValue(\n attributes,\n 'isVisible',\n true\n );\n const isAnimating = attributes.isAnimating ?? false;\n const maxClicks = attributes.maxClicks ?? 0;\n const showCounter = editorFields.getBooleanValue(\n attributes,\n 'showCounter',\n true\n );\n const interactiveMode = editorFields.getStringValue(\n attributes,\n 'interactiveMode',\n 'click'\n ) as NonNullable<{{pascalCase}}Attributes['interactiveMode']>;\n const animation = editorFields.getStringValue(\n attributes,\n 'animation',\n 'none'\n ) as NonNullable<{{pascalCase}}Attributes['animation']>;\n\n const blockProps = useBlockProps({\n className: `{{cssClassName}} {{cssClassName}}--${interactiveMode}`,\n 'data-wp-interactive': '{{slugKebabCase}}',\n 'data-wp-context': JSON.stringify({\n clicks: clickCount,\n isAnimating,\n isVisible,\n animation,\n maxClicks,\n })\n });\n const previewContentStyle = { textAlign: alignmentValue };\n const progressBarStyle = { width: `${(clickCount / maxClicks) * 100}%` };\n\n const resetCounter = () => {\n updateField('clickCount', 0);\n updateField('isAnimating', false);\n };\n\n const testAnimation = () => {\n updateField('isAnimating', true);\n setTimeout(() => {\n updateField('isAnimating', false);\n }, 1000);\n };\n\n return (\n <>\n <BlockControls>\n <AlignmentToolbar\n value={alignmentValue}\n onChange={(value) => updateField('alignment', (value || alignmentValue) as NonNullable<{{pascalCase}}Attributes['alignment']>)}\n />\n </BlockControls>\n\n <InspectorControls>\n <InspectorFromManifest\n attributes={attributes}\n fieldLookup={editorFields}\n onChange={updateField}\n paths={['alignment', 'interactiveMode', 'animation', 'showCounter', 'isVisible']}\n title={__('Interactive Settings', '{{textDomain}}')}\n />\n\n <PanelBody title={__('Counter Settings', '{{textDomain}}')}>\n <RangeControl\n label={__('Max Clicks', '{{textDomain}}')}\n value={maxClicks}\n onChange={(value) => updateField('maxClicks', value ?? maxClicks)}\n min={0}\n max={100}\n help={__('Set to 0 for unlimited clicks', '{{textDomain}}')}\n />\n\n <div style={actionButtonRowStyle}>\n <Button\n variant=\"secondary\"\n onClick={resetCounter}\n isSmall\n >\n {__('Reset Counter', '{{textDomain}}')}\n </Button>\n <Button\n variant=\"secondary\"\n onClick={testAnimation}\n isSmall\n >\n {__('Test Animation', '{{textDomain}}')}\n </Button>\n </div>\n </PanelBody>\n\n {!isValid && (\n <PanelBody title={__('Validation Errors', '{{textDomain}}')} initialOpen>\n {errorMessages.map((error, index) => (\n <Notice key={index} status=\"error\" isDismissible={false}>\n {error}\n </Notice>\n ))}\n </PanelBody>\n )}\n\n {isSelected && (\n <PanelBody title={__('Preview', '{{textDomain}}')}>\n <Button\n variant={isPreviewing ? 'primary' : 'secondary'}\n onClick={() => setIsPreviewing(!isPreviewing)}\n aria-pressed={isPreviewing}\n isSmall\n >\n {isPreviewing\n ? __('Disable Preview Mode', '{{textDomain}}')\n : __('Enable Preview Mode', '{{textDomain}}')}\n </Button>\n\n {clickCount > 0 && (\n <Notice status=\"info\" isDismissible={false}>\n {__('Current clicks:', '{{textDomain}}')} {clickCount}\n {maxClicks > 0 && ` / ${maxClicks}`}\n </Notice>\n )}\n </PanelBody>\n )}\n </InspectorControls>\n\n <div {...blockProps}>\n <div\n className={`{{cssClassName}}__content ${isAnimating ? 'is-animating' : ''}`}\n style={previewContentStyle}\n data-wp-on--click={isPreviewing ? 'actions.handleClick' : undefined}\n data-wp-on--mouseenter={isPreviewing && interactiveMode === 'hover' ? 'actions.handleMouseEnter' : undefined}\n data-wp-on--mouseleave={isPreviewing && interactiveMode === 'hover' ? 'actions.handleMouseLeave' : undefined}\n >\n <RichText\n tagName=\"p\"\n value={attributes.content}\n onChange={(value) => updateField('content', value)}\n placeholder={__( {{titleJson}} + ' \u2013 click me to interact!', '{{textDomain}}')}\n />\n\n {!isValid && (\n <Notice status=\"error\" isDismissible={false}>\n <p>\n <strong>{__('Validation Errors', '{{textDomain}}')}</strong>\n </p>\n <ul style={validationListStyle}>\n {errorMessages.map((error, index) => (\n <li key={index}>{error}</li>\n ))}\n </ul>\n </Notice>\n )}\n\n {showCounter && (\n <div className=\"{{cssClassName}}__counter\">\n <span className=\"{{cssClassName}}__counter-label\">\n {__('Clicks:', '{{textDomain}}')}\n </span>\n <span\n className=\"{{cssClassName}}__counter-value\"\n data-wp-text=\"state.clicks\"\n >\n {clickCount}\n </span>\n </div>\n )}\n\n {maxClicks > 0 && (\n <div className=\"{{cssClassName}}__progress\">\n <div\n className=\"{{cssClassName}}__progress-bar\"\n style={progressBarStyle}\n data-wp-style--width=\"state.progress + '%'\"\n />\n </div>\n )}\n\n {animation !== 'none' && (\n <div\n className={`{{cssClassName}}__animation ${isAnimating ? 'is-active' : ''}`}\n data-wp-class--is-active=\"state.isAnimating\"\n >\n {animation}\n </div>\n )}\n </div>\n </div>\n </>\n );\n}\n";
14
14
  export declare const INTERACTIVITY_SAVE_TEMPLATE = "import { useBlockProps, RichText } from '@wordpress/block-editor';\nimport { __ } from '@wordpress/i18n';\nimport type { {{pascalCase}}Attributes } from './types';\n\nexport default function Save({ attributes }: { attributes: {{pascalCase}}Attributes }) {\n const clickCount = attributes.clickCount ?? 0;\n const interactiveMode = attributes.interactiveMode ?? 'click';\n const animation = attributes.animation ?? 'none';\n const isAnimating = attributes.isAnimating ?? false;\n const isVisible = attributes.isVisible ?? true;\n const maxClicks = attributes.maxClicks ?? 0;\n const showCounter = attributes.showCounter ?? true;\n const contentStyle = { textAlign: attributes.alignment };\n const blockProps = useBlockProps.save({\n className: `{{cssClassName}} {{cssClassName}}--${interactiveMode}`,\n 'data-wp-interactive': '{{slugKebabCase}}',\n 'data-wp-context': JSON.stringify({\n clicks: clickCount,\n isAnimating,\n isVisible,\n animation,\n maxClicks,\n })\n });\n\n return (\n <div {...blockProps}>\n <div\n className={`{{cssClassName}}__content ${isAnimating ? 'is-animating' : ''}`}\n style={contentStyle}\n data-wp-on--click=\"actions.handleClick\"\n data-wp-on--mouseenter={interactiveMode === 'hover' ? 'actions.handleMouseEnter' : undefined}\n data-wp-on--mouseleave={interactiveMode === 'hover' ? 'actions.handleMouseLeave' : undefined}\n data-wp-bind--hidden=\"!state.isVisible\"\n >\n <RichText.Content\n tagName=\"p\"\n value={attributes.content}\n className=\"{{cssClassName}}__text\"\n />\n\n {showCounter && (\n <div\n className=\"{{cssClassName}}__counter\"\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n >\n <span className=\"{{cssClassName}}__counter-label\">\n { __( 'Clicks:', '{{textDomain}}' ) }\n </span>\n <span\n className=\"{{cssClassName}}__counter-value\"\n data-wp-text=\"state.clicks\"\n >\n {clickCount}\n </span>\n </div>\n )}\n\n {maxClicks > 0 && (\n <div className=\"{{cssClassName}}__progress\">\n <div\n className=\"{{cssClassName}}__progress-bar\"\n role=\"progressbar\"\n aria-label={ __( 'Click progress', '{{textDomain}}' ) }\n aria-valuemin={0}\n aria-valuemax={maxClicks}\n aria-valuenow={Math.min(clickCount, maxClicks)}\n data-wp-bind--aria-valuenow=\"state.clampedClicks\"\n data-wp-style--width=\"state.progress + '%'\"\n />\n </div>\n )}\n\n <div\n className={`{{cssClassName}}__animation ${animation}`}\n aria-hidden=\"true\"\n data-wp-class--is-active=\"state.isAnimating\"\n />\n\n {maxClicks > 0 && (\n <div\n className=\"{{cssClassName}}__completion\"\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n data-wp-bind--hidden=\"!state.isComplete\"\n >\n { __( '\uD83C\uDF89 Complete!', '{{textDomain}}' ) }\n </div>\n )}\n\n <button\n className=\"{{cssClassName}}__reset\"\n data-wp-on--click=\"actions.reset\"\n aria-label={ __( 'Reset counter', '{{textDomain}}' ) }\n >\n <span aria-hidden=\"true\">\u21BB</span>\n <span className=\"screen-reader-text\">\n { __( 'Reset counter', '{{textDomain}}' ) }\n </span>\n </button>\n </div>\n </div>\n );\n}\n";
15
- export declare const INTERACTIVITY_INDEX_TEMPLATE = "import { registerBlockType } from '@wordpress/blocks';\nimport type { BlockConfiguration } from '@wp-typia/block-types/blocks/registration';\nimport type { BlockSupports } from '@wp-typia/block-types/blocks/supports';\nimport {\n buildScaffoldBlockRegistration,\n parseScaffoldBlockMetadata,\n} from '@wp-typia/block-runtime/blocks';\n\nimport Edit from './edit';\nimport Save from './save';\nimport metadata from './block-metadata';\nimport './editor.scss';\nimport './style.scss';\n\nimport type { {{pascalCase}}Attributes } from './types';\n\nconst scaffoldSupports = {\n html: false,\n align: true,\n anchor: true,\n className: true,\n interactivity: true,\n} satisfies BlockSupports;\n\nconst registration = buildScaffoldBlockRegistration(\n parseScaffoldBlockMetadata<BlockConfiguration<{{pascalCase}}Attributes>>(metadata),\n {\n supports: scaffoldSupports,\n edit: Edit,\n save: Save,\n }\n);\n\nregisterBlockType<{{pascalCase}}Attributes>(registration.name, registration.settings);\n";
15
+ export declare const INTERACTIVITY_INDEX_TEMPLATE = "import {\n registerScaffoldBlockType,\n type BlockConfiguration,\n} from '@wp-typia/block-types/blocks/registration';\nimport type { BlockSupports } from '@wp-typia/block-types/blocks/supports';\nimport {\n buildScaffoldBlockRegistration,\n parseScaffoldBlockMetadata,\n} from '@wp-typia/block-runtime/blocks';\n\nimport Edit from './edit';\nimport Save from './save';\nimport metadata from './block-metadata';\nimport './editor.scss';\nimport './style.scss';\n\nimport type { {{pascalCase}}Attributes } from './types';\n\nconst scaffoldSupports = {\n html: false,\n align: true,\n anchor: true,\n className: true,\n interactivity: true,\n} satisfies BlockSupports;\n\nconst registration = buildScaffoldBlockRegistration(\n parseScaffoldBlockMetadata<BlockConfiguration<{{pascalCase}}Attributes>>(metadata),\n {\n supports: scaffoldSupports,\n edit: Edit,\n save: Save,\n }\n);\n\nregisterScaffoldBlockType(registration.name, registration.settings);\n";
16
16
  export declare const INTERACTIVITY_SCRIPT_TEMPLATE = "/**\n * WordPress Interactivity API implementation for {{title}} block\n */\nimport { store, getContext, getElement, withSyncEvent } from '@wordpress/interactivity';\nimport type { {{pascalCase}}Context } from './types';\n\nfunction getBlockContext() {\n return getContext<{{pascalCase}}Context>();\n}\n\n// Store configuration\nstore('{{slugKebabCase}}', {\n // State - reactive data that updates the UI\n state: {\n get clicks() {\n return getBlockContext().clicks;\n },\n get isAnimating() {\n return getBlockContext().isAnimating;\n },\n get isVisible() {\n return getBlockContext().isVisible;\n },\n get progress() {\n const context = getBlockContext();\n const clampedClicks =\n context.maxClicks > 0\n ? Math.min(context.clicks, context.maxClicks)\n : context.clicks;\n return context.maxClicks > 0 ? (clampedClicks / context.maxClicks) * 100 : 0;\n },\n get clampedClicks() {\n const context = getBlockContext();\n return context.maxClicks > 0\n ? Math.min(context.clicks, context.maxClicks)\n : context.clicks;\n },\n get isComplete() {\n const context = getBlockContext();\n return context.clicks >= context.maxClicks && context.maxClicks > 0;\n }\n },\n\n // Actions - user interactions\n actions: {\n // Handle block click\n handleClick: () => {\n const context = getBlockContext();\n const { ref } = getElement();\n\n if (!ref) {\n return;\n }\n\n if (context.maxClicks > 0 && context.clicks >= context.maxClicks) {\n return;\n }\n\n const previousClicks = context.clicks;\n\n // Increment click counter\n context.clicks += 1;\n\n // Trigger animation\n if (context.animation !== 'none') {\n context.isAnimating = true;\n setTimeout(() => {\n context.isAnimating = false;\n }, 1000);\n }\n\n // Emit custom event\n ref.dispatchEvent(new CustomEvent('{{slugKebabCase}}:click', {\n detail: { clicks: context.clicks }\n }));\n\n // Check if max clicks reached\n if (context.maxClicks > 0 && previousClicks < context.maxClicks && context.clicks === context.maxClicks) {\n ref.dispatchEvent(new CustomEvent('{{slugKebabCase}}:complete', {\n detail: { totalClicks: context.clicks }\n }));\n }\n },\n\n // Handle hover events\n handleMouseEnter: () => {\n const context = getBlockContext();\n if (context.animation === 'none') return;\n context.isAnimating = true;\n },\n\n handleMouseLeave: () => {\n const context = getBlockContext();\n if (context.animation === 'none') return;\n context.isAnimating = false;\n },\n\n // Reset counter\n reset: withSyncEvent((event: Event) => {\n event.stopPropagation();\n const context = getBlockContext();\n context.clicks = 0;\n context.isAnimating = false;\n })\n }\n});\n";
17
17
  export declare const INTERACTIVITY_VALIDATORS_TEMPLATE = "import typia from 'typia';\nimport currentManifest from \"./manifest-defaults-document\";\nimport { {{pascalCase}}Attributes, {{pascalCase}}ValidationResult } from \"./types\";\nimport { createTemplateValidatorToolkit } from \"./validator-toolkit\";\n\nconst scaffoldValidators = createTemplateValidatorToolkit<{{pascalCase}}Attributes>({\n assert: typia.createAssert<{{pascalCase}}Attributes>(),\n clone: typia.misc.createClone<{{pascalCase}}Attributes>() as (\n value: {{pascalCase}}Attributes,\n ) => {{pascalCase}}Attributes,\n is: typia.createIs<{{pascalCase}}Attributes>(),\n manifest: currentManifest,\n prune: typia.misc.createPrune<{{pascalCase}}Attributes>(),\n random: typia.createRandom<{{pascalCase}}Attributes>() as (\n ...args: unknown[]\n ) => {{pascalCase}}Attributes,\n validate: typia.createValidate<{{pascalCase}}Attributes>(),\n});\n\nexport const validate{{pascalCase}}Attributes =\n scaffoldValidators.validateAttributes as (\n attributes: unknown,\n ) => {{pascalCase}}ValidationResult;\n\nexport const validators = scaffoldValidators.validators;\n\nexport const sanitize{{pascalCase}}Attributes =\n scaffoldValidators.sanitizeAttributes as (\n attributes: Partial<{{pascalCase}}Attributes>,\n ) => {{pascalCase}}Attributes;\n\n/**\n * Runtime type guard for checking if an object is {{pascalCase}}Attributes.\n */\nexport const is{{pascalCase}}Attributes = (obj: unknown): obj is {{pascalCase}}Attributes => {\n return validators.is(obj);\n};\n\nexport const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;\n";
18
18
  export declare const PERSISTENCE_EDIT_TEMPLATE = "import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';\nimport { __ } from '@wordpress/i18n';\nimport {\n\tAlignmentToolbar,\n\tBlockControls,\n\tInspectorControls,\n\tRichText,\n\tuseBlockProps,\n} from '@wordpress/block-editor';\nimport {\n\tNotice,\n\tPanelBody,\n\tTextControl,\n} from '@wordpress/components';\nimport currentManifest from './manifest-document';\nimport {\n\tInspectorFromManifest,\n\tuseEditorFields,\n\tuseTypedAttributeUpdater,\n} from '@wp-typia/block-runtime/inspector';\nimport type { {{pascalCase}}Attributes } from './types';\nimport {\n\tsanitize{{pascalCase}}Attributes,\n\tvalidate{{pascalCase}}Attributes,\n} from './validators';\nimport { useTypiaValidation } from './hooks';\n\ntype EditProps = BlockEditProps< {{pascalCase}}Attributes >;\n\nexport default function Edit( {\n\tattributes,\n\tsetAttributes,\n}: EditProps ) {\n\tconst editorFields = useEditorFields(\n\t\tcurrentManifest,\n\t\t{\n\t\t\tmanual: [ 'content', 'resourceKey' ],\n\t\t\tlabels: {\n\t\t\t\tbuttonLabel: __( 'Button Label', '{{textDomain}}' ),\n\t\t\t\tresourceKey: __( 'Resource Key', '{{textDomain}}' ),\n\t\t\t\tshowCount: __( 'Show Count', '{{textDomain}}' ),\n\t\t\t},\n\t\t}\n\t);\n\tconst { errorMessages, isValid } = useTypiaValidation(\n\t\tattributes,\n\t\tvalidate{{pascalCase}}Attributes\n\t);\n\tconst validateEditorUpdate = (\n\t\tnextAttributes: {{pascalCase}}Attributes\n\t) => {\n\t\ttry {\n\t\t\treturn {\n\t\t\t\tdata: sanitize{{pascalCase}}Attributes( nextAttributes ),\n\t\t\t\terrors: [],\n\t\t\t\tisValid: true as const,\n\t\t\t};\n\t\t} catch {\n\t\t\treturn validate{{pascalCase}}Attributes( nextAttributes );\n\t\t}\n\t};\n\tconst { updateField } = useTypedAttributeUpdater(\n\t\tattributes,\n\t\tsetAttributes,\n\t\tvalidateEditorUpdate\n\t);\n\tconst alignmentValue = editorFields.getStringValue(\n\t\tattributes,\n\t\t'alignment',\n\t\t'left'\n\t);\n\tconst persistencePolicy = '{{persistencePolicy}}';\n\tconst persistencePolicyDescription = __(\n\t\t{{persistencePolicyDescriptionJson}},\n\t\t'{{textDomain}}'\n\t);\n\n\treturn (\n\t\t<>\n\t\t\t<BlockControls>\n\t\t\t\t<AlignmentToolbar\n\t\t\t\t\tvalue={ alignmentValue }\n\t\t\t\t\tonChange={ ( value ) =>\n\t\t\t\t\t\tupdateField(\n\t\t\t\t\t\t\t'alignment',\n\t\t\t\t\t\t\t( value || alignmentValue ) as NonNullable< {{pascalCase}}Attributes[ 'alignment' ] >\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t/>\n\t\t\t</BlockControls>\n\t\t\t<InspectorControls>\n\t\t\t\t<InspectorFromManifest\n\t\t\t\t\tattributes={ attributes }\n\t\t\t\t\tfieldLookup={ editorFields }\n\t\t\t\t\tonChange={ updateField }\n\t\t\t\t\tpaths={ [ 'alignment', 'isVisible', 'showCount', 'buttonLabel' ] }\n\t\t\t\t\ttitle={ __( 'Persistence Settings', '{{textDomain}}' ) }\n\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={ ( value ) => updateField( 'resourceKey', value ) }\n\t\t\t\t\t\thelp={ __( 'Stable persisted identifier used by the storage-backed 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\t<br />\n\t\t\t\t\t\t{ persistencePolicyDescription }\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{ __( 'Render mode: dynamic. `render.php` bootstraps durable post context, while fresh session-only write data is loaded from the dedicated `/bootstrap` endpoint after hydration.', '{{textDomain}}' ) }\n\t\t\t\t\t</Notice>\n\t\t\t\t</InspectorFromManifest>\n\t\t\t\t{ ! isValid && (\n\t\t\t\t\t<PanelBody\n\t\t\t\t\t\ttitle={ __( 'Validation Errors', '{{textDomain}}' ) }\n\t\t\t\t\t\tinitialOpen\n\t\t\t\t\t>\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\n\t\t\t\t{ ...useBlockProps( {\n\t\t\t\t\tclassName: '{{cssClassName}}',\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\ttextAlign:\n\t\t\t\t\t\t\talignmentValue as NonNullable< {{pascalCase}}Attributes[ 'alignment' ] >,\n\t\t\t\t\t},\n\t\t\t\t} ) }\n\t\t\t>\n\t\t\t\t<RichText\n\t\t\t\t\ttagName=\"p\"\n\t\t\t\t\tvalue={ attributes.content }\n\t\t\t\t\tonChange={ ( value ) => updateField( 'content', value ) }\n\t\t\t\t\tplaceholder={ __( {{titleJson}} + ' persistence block', '{{textDomain}}' ) }\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<p className=\"{{cssClassName}}__meta\">\n\t\t\t\t\t{ __( 'Storage mode:', '{{textDomain}}' ) } {{dataStorageMode}}\n\t\t\t\t</p>\n\t\t\t\t<p className=\"{{cssClassName}}__meta\">\n\t\t\t\t\t{ __( 'Persistence policy:', '{{textDomain}}' ) } {{persistencePolicy}}\n\t\t\t\t</p>\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</div>\n\t\t</>\n\t);\n}\n";
19
- export declare const PERSISTENCE_INDEX_TEMPLATE = "import { registerBlockType } from '@wordpress/blocks';\nimport type { BlockConfiguration } 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\nregisterBlockType< {{pascalCase}}Attributes >(\n\tregistration.name,\n\tregistration.settings\n);\n";
19
+ export declare const PERSISTENCE_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";
20
20
  export declare const PERSISTENCE_SAVE_TEMPLATE = "export default function Save() {\n\t// This block is intentionally server-rendered. PHP bootstraps post context,\n\t// storage-backed state, and write-policy data before the frontend hydrates.\n\treturn null;\n}\n";
21
21
  export declare const PERSISTENCE_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";
22
22
  export declare const PERSISTENCE_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';\nimport type {\n\t{{pascalCase}}WriteStateRequest,\n} from './api-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 request = {\n\t\t\t\t\tdelta: 1,\n\t\t\t\t\tpostId: context.postId,\n\t\t\t\t\tresourceKey: context.resourceKey,\n\t\t\t\t} as {{pascalCase}}WriteStateRequest;\n\t\t\t\tif ( {{isPublicPersistencePolicy}} ) {\n\t\t\t\t\trequest.publicWriteRequestId =\n\t\t\t\t\t\tgeneratePublicWriteRequestId() as {{pascalCase}}WriteStateRequest[ 'publicWriteRequestId' ];\n\t\t\t\t\tif ( clientState.writeToken.length > 0 ) {\n\t\t\t\t\t\trequest.publicWriteToken =\n\t\t\t\t\t\t\tclientState.writeToken as {{pascalCase}}WriteStateRequest[ 'publicWriteToken' ];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst result = await writeState( request, {\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";
23
23
  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";
24
24
  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";
25
- export declare const COMPOUND_PARENT_INDEX_TEMPLATE = "import { registerBlockType } from '@wordpress/blocks';\nimport type { BlockConfiguration } 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\nregisterBlockType< {{pascalCase}}Attributes >(\n\tregistration.name,\n\tregistration.settings\n);\n";
25
+ 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";
26
26
  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";
27
27
  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";
28
28
  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 const ALLOWED_CHILD_BLOCKS = [\n\tDEFAULT_CHILD_BLOCK_NAME,\n\t// add-child: insert new allowed child block names here\n];\n\nexport const DEFAULT_CHILD_TEMPLATE: BlockTemplate = [\n\t[\n\t\tDEFAULT_CHILD_BLOCK_NAME,\n\t\t{\n\t\t\tbody: 'Add supporting details for the first internal item.',\n\t\t\ttitle: 'First Item',\n\t\t},\n\t],\n\t[\n\t\tDEFAULT_CHILD_BLOCK_NAME,\n\t\t{\n\t\t\tbody: 'Add supporting details for the second internal item.',\n\t\t\ttitle: 'Second Item',\n\t\t},\n\t],\n];\n";
29
29
  export declare const COMPOUND_CHILD_EDIT_TEMPLATE = "import type { BlockEditProps } from '@wp-typia/block-types/blocks/registration';\nimport { RichText, useBlockProps } from '@wordpress/block-editor';\nimport { Notice } from '@wordpress/components';\nimport { __ } from '@wordpress/i18n';\n\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\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</div>\n\t);\n}\n";
30
30
  export declare const COMPOUND_CHILD_SAVE_TEMPLATE = "import { RichText, useBlockProps } from '@wordpress/block-editor';\n\nimport type { {{pascalCase}}ItemAttributes } from './types';\n\nexport default function Save( {\n\tattributes,\n}: {\n\tattributes: {{pascalCase}}ItemAttributes;\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</div>\n\t);\n}\n";
31
- export declare const COMPOUND_CHILD_INDEX_TEMPLATE = "import { registerBlockType } from '@wordpress/blocks';\nimport type { BlockConfiguration } 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\nregisterBlockType< {{pascalCase}}ItemAttributes >(\n\tregistration.name,\n\tregistration.settings\n);\n";
31
+ 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";
32
32
  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";
33
33
  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";
34
34
  export declare const COMPOUND_PERSISTENCE_PARENT_SAVE_TEMPLATE = "export default function Save() {\n\treturn null;\n}\n";
@@ -211,8 +211,10 @@ export const BASIC_INDEX_TEMPLATE = `/**
211
211
  * Typia-powered type-safe block
212
212
  */
213
213
 
214
- import { registerBlockType } from '@wordpress/blocks';
215
- import type { BlockConfiguration } from '@wp-typia/block-types/blocks/registration';
214
+ import {
215
+ registerScaffoldBlockType,
216
+ type BlockConfiguration,
217
+ } from '@wp-typia/block-types/blocks/registration';
216
218
  import type { BlockSupports } from '@wp-typia/block-types/blocks/supports';
217
219
  import { __ } from '@wordpress/i18n';
218
220
  import {
@@ -250,7 +252,7 @@ const registration = buildScaffoldBlockRegistration(
250
252
  }
251
253
  );
252
254
 
253
- registerBlockType<{{pascalCase}}Attributes>(registration.name, registration.settings);
255
+ registerScaffoldBlockType(registration.name, registration.settings);
254
256
  `;
255
257
  export const BASIC_VALIDATORS_TEMPLATE = `import typia from 'typia';
256
258
  import currentManifest from "./manifest-defaults-document";
@@ -653,8 +655,10 @@ export default function Save({ attributes }: { attributes: {{pascalCase}}Attribu
653
655
  );
654
656
  }
655
657
  `;
656
- export const INTERACTIVITY_INDEX_TEMPLATE = `import { registerBlockType } from '@wordpress/blocks';
657
- import type { BlockConfiguration } from '@wp-typia/block-types/blocks/registration';
658
+ export const INTERACTIVITY_INDEX_TEMPLATE = `import {
659
+ registerScaffoldBlockType,
660
+ type BlockConfiguration,
661
+ } from '@wp-typia/block-types/blocks/registration';
658
662
  import type { BlockSupports } from '@wp-typia/block-types/blocks/supports';
659
663
  import {
660
664
  buildScaffoldBlockRegistration,
@@ -686,7 +690,7 @@ const registration = buildScaffoldBlockRegistration(
686
690
  }
687
691
  );
688
692
 
689
- registerBlockType<{{pascalCase}}Attributes>(registration.name, registration.settings);
693
+ registerScaffoldBlockType(registration.name, registration.settings);
690
694
  `;
691
695
  export const INTERACTIVITY_SCRIPT_TEMPLATE = `/**
692
696
  * WordPress Interactivity API implementation for {{title}} block
@@ -1000,8 +1004,10 @@ export default function Edit( {
1000
1004
  );
1001
1005
  }
1002
1006
  `;
1003
- export const PERSISTENCE_INDEX_TEMPLATE = `import { registerBlockType } from '@wordpress/blocks';
1004
- import type { BlockConfiguration } from '@wp-typia/block-types/blocks/registration';
1007
+ export const PERSISTENCE_INDEX_TEMPLATE = `import {
1008
+ \tregisterScaffoldBlockType,
1009
+ \ttype BlockConfiguration,
1010
+ } from '@wp-typia/block-types/blocks/registration';
1005
1011
  import {
1006
1012
  buildScaffoldBlockRegistration,
1007
1013
  parseScaffoldBlockMetadata,
@@ -1022,10 +1028,7 @@ const registration = buildScaffoldBlockRegistration(
1022
1028
  }
1023
1029
  );
1024
1030
 
1025
- registerBlockType< {{pascalCase}}Attributes >(
1026
- registration.name,
1027
- registration.settings
1028
- );
1031
+ registerScaffoldBlockType(registration.name, registration.settings);
1029
1032
  `;
1030
1033
  export const PERSISTENCE_SAVE_TEMPLATE = `export default function Save() {
1031
1034
  // This block is intentionally server-rendered. PHP bootstraps post context,
@@ -1513,8 +1516,10 @@ export default function Save( {
1513
1516
  );
1514
1517
  }
1515
1518
  `;
1516
- export const COMPOUND_PARENT_INDEX_TEMPLATE = `import { registerBlockType } from '@wordpress/blocks';
1517
- import type { BlockConfiguration } from '@wp-typia/block-types/blocks/registration';
1519
+ export const COMPOUND_PARENT_INDEX_TEMPLATE = `import {
1520
+ \tregisterScaffoldBlockType,
1521
+ \ttype BlockConfiguration,
1522
+ } from '@wp-typia/block-types/blocks/registration';
1518
1523
  import {
1519
1524
  buildScaffoldBlockRegistration,
1520
1525
  parseScaffoldBlockMetadata,
@@ -1535,10 +1540,7 @@ const registration = buildScaffoldBlockRegistration(
1535
1540
  }
1536
1541
  );
1537
1542
 
1538
- registerBlockType< {{pascalCase}}Attributes >(
1539
- registration.name,
1540
- registration.settings
1541
- );
1543
+ registerScaffoldBlockType(registration.name, registration.settings);
1542
1544
  `;
1543
1545
  export const COMPOUND_LOCAL_HOOKS_TEMPLATE = `export {
1544
1546
  formatValidationError,
@@ -1690,8 +1692,10 @@ export default function Save( {
1690
1692
  );
1691
1693
  }
1692
1694
  `;
1693
- export const COMPOUND_CHILD_INDEX_TEMPLATE = `import { registerBlockType } from '@wordpress/blocks';
1694
- import type { BlockConfiguration } from '@wp-typia/block-types/blocks/registration';
1695
+ export const COMPOUND_CHILD_INDEX_TEMPLATE = `import {
1696
+ \tregisterScaffoldBlockType,
1697
+ \ttype BlockConfiguration,
1698
+ } from '@wp-typia/block-types/blocks/registration';
1695
1699
  import {
1696
1700
  buildScaffoldBlockRegistration,
1697
1701
  parseScaffoldBlockMetadata,
@@ -1712,10 +1716,7 @@ const registration = buildScaffoldBlockRegistration(
1712
1716
  }
1713
1717
  );
1714
1718
 
1715
- registerBlockType< {{pascalCase}}ItemAttributes >(
1716
- registration.name,
1717
- registration.settings
1718
- );
1719
+ registerScaffoldBlockType(registration.name, registration.settings);
1719
1720
  `;
1720
1721
  export const COMPOUND_CHILD_VALIDATORS_TEMPLATE = `import typia from 'typia';
1721
1722
  import currentManifest from './manifest-defaults-document';
@@ -33,7 +33,7 @@ Built-in templates: ${TEMPLATE_IDS.join(", ")}
33
33
  Package managers: ${PACKAGE_MANAGER_IDS.join(", ")}
34
34
  Notes:
35
35
  \`wp-typia create\` is the canonical scaffold command.
36
- \`wp-typia <project-dir>\` remains a backward-compatible alias to \`create\`.
36
+ \`wp-typia <project-dir>\` remains a backward-compatible alias to \`create\` when \`<project-dir>\` is the only positional argument.
37
37
  Use \`--template workspace\` as shorthand for \`@wp-typia/create-workspace-template\`, the official empty workspace scaffold behind \`wp-typia add ...\`.
38
38
  \`add variation\` uses an existing workspace block from \`scripts/block-config.ts\`.
39
39
  \`add pattern\` scaffolds a namespaced PHP pattern shell under \`src/patterns/\`.
@@ -108,7 +108,7 @@ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templ
108
108
  ? externalLayerSource.trim()
109
109
  : undefined;
110
110
  if (!projectInput) {
111
- throw new Error("Project directory is required. Usage: wp-typia create <project-dir> (or wp-typia <project-dir>).");
111
+ throw new Error("Project directory is required. Usage: wp-typia create <project-dir> (or wp-typia <project-dir> when <project-dir> is the only positional argument).");
112
112
  }
113
113
  const resolvedTemplateId = await resolveTemplateId({
114
114
  templateId,
@@ -41,7 +41,7 @@ function injectBefore(source, needle, insertion) {
41
41
  return source.replace(needle, `${insertion}\n${needle}`);
42
42
  }
43
43
  function injectAfterBlockMetadataImport(source, insertion) {
44
- let nextSource = injectAfter(source, BLOCK_METADATA_IMPORT_LINE, insertion);
44
+ const nextSource = injectAfter(source, BLOCK_METADATA_IMPORT_LINE, insertion);
45
45
  if (nextSource !== source) {
46
46
  return nextSource;
47
47
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wp-typia/project-tools",
3
- "version": "0.16.9",
3
+ "version": "0.16.11",
4
4
  "description": "Project orchestration and programmatic tooling for wp-typia",
5
5
  "packageManager": "bun@1.3.11",
6
6
  "type": "module",
@@ -121,8 +121,8 @@
121
121
  "dependencies": {
122
122
  "@wp-typia/api-client": "^0.4.4",
123
123
  "@wp-typia/block-runtime": "^0.4.8",
124
- "@wp-typia/rest": "^0.3.7",
125
- "@wp-typia/block-types": "^0.2.2",
124
+ "@wp-typia/rest": "^0.3.8",
125
+ "@wp-typia/block-types": "^0.2.4",
126
126
  "mustache": "^4.2.0",
127
127
  "npm-package-arg": "^13.0.0",
128
128
  "semver": "^7.7.3",
@@ -576,8 +576,10 @@ export default function Save( {
576
576
  }
577
577
 
578
578
  function renderIndexFile( childTypeName: string, childFolderSlug: string ): string {
579
- return `import { registerBlockType } from '@wordpress/blocks';
580
- import type { BlockConfiguration } from '@wp-typia/block-types/blocks/registration';
579
+ return `import {
580
+ \tregisterScaffoldBlockType,
581
+ \ttype BlockConfiguration,
582
+ } from '@wp-typia/block-types/blocks/registration';
581
583
  import {
582
584
  \tbuildScaffoldBlockRegistration,
583
585
  \tparseScaffoldBlockMetadata,
@@ -598,10 +600,7 @@ const registration = buildScaffoldBlockRegistration(
598
600
  \t}
599
601
  );
600
602
 
601
- registerBlockType< ${ childTypeName } >(
602
- \tregistration.name,
603
- \tregistration.settings
604
- );
603
+ registerScaffoldBlockType( registration.name, registration.settings );
605
604
  `;
606
605
  }
607
606