create-wp-typia 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +33 -0
  2. package/dist/cli.js +87837 -0
  3. package/dist/highlights-eq9cgrbb.scm +604 -0
  4. package/dist/highlights-ghv9g403.scm +205 -0
  5. package/dist/highlights-hk7bwhj4.scm +284 -0
  6. package/dist/highlights-r812a2qc.scm +150 -0
  7. package/dist/highlights-x6tmsnaa.scm +115 -0
  8. package/dist/injections-73j83es3.scm +27 -0
  9. package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  10. package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
  11. package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  12. package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  13. package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
  14. package/lib/entry.js +29 -0
  15. package/lib/node-cli.js +326 -0
  16. package/lib/package-managers.d.ts +29 -0
  17. package/lib/package-managers.js +170 -0
  18. package/lib/scaffold.d.ts +64 -0
  19. package/lib/scaffold.js +565 -0
  20. package/lib/template-registry.d.ts +18 -0
  21. package/lib/template-registry.js +58 -0
  22. package/package.json +64 -0
  23. package/src/cli.ts +329 -0
  24. package/templates/advanced/README.md.mustache +70 -0
  25. package/templates/advanced/block.json.mustache +42 -0
  26. package/templates/advanced/index.js +21 -0
  27. package/templates/advanced/package.json.mustache +48 -0
  28. package/templates/advanced/scripts/generate-migrations.ts.mustache +267 -0
  29. package/templates/advanced/scripts/lib/typia-metadata-core.ts +806 -0
  30. package/templates/advanced/scripts/migration-cli.ts.mustache +260 -0
  31. package/templates/advanced/scripts/sync-types-to-block-json.ts.mustache +25 -0
  32. package/templates/advanced/src/admin/migration-dashboard.tsx.mustache +450 -0
  33. package/templates/advanced/src/components/ErrorBoundary.tsx.mustache +47 -0
  34. package/templates/advanced/src/deprecated.ts.mustache +184 -0
  35. package/templates/advanced/src/edit.tsx.mustache +93 -0
  36. package/templates/advanced/src/hooks/useDebounce.ts.mustache +20 -0
  37. package/templates/advanced/src/hooks/useLocalStorage.ts.mustache +31 -0
  38. package/templates/advanced/src/hooks.ts.mustache +56 -0
  39. package/templates/advanced/src/index.tsx.mustache +16 -0
  40. package/templates/advanced/src/migration-detector.ts.mustache +417 -0
  41. package/templates/advanced/src/migrations/index.ts.mustache +361 -0
  42. package/templates/advanced/src/save.tsx.mustache +40 -0
  43. package/templates/advanced/src/style.scss.mustache +84 -0
  44. package/templates/advanced/src/types/versions.ts.mustache +108 -0
  45. package/templates/advanced/src/types.ts.mustache +45 -0
  46. package/templates/advanced/src/utils/classnames.ts.mustache +51 -0
  47. package/templates/advanced/src/utils/debounce.ts.mustache +37 -0
  48. package/templates/advanced/src/utils/index.ts.mustache +7 -0
  49. package/templates/advanced/src/utils/uuid.ts.mustache +17 -0
  50. package/templates/advanced/src/validators.ts.mustache +39 -0
  51. package/templates/advanced/src/view.ts.mustache +59 -0
  52. package/templates/advanced/tsconfig.json.mustache +9 -0
  53. package/templates/advanced/webpack.config.js.mustache +85 -0
  54. package/templates/basic/package.json.mustache +39 -0
  55. package/templates/basic/scripts/lib/typia-metadata-core.ts +806 -0
  56. package/templates/basic/scripts/sync-types-to-block-json.ts +25 -0
  57. package/templates/basic/src/block.json +51 -0
  58. package/templates/basic/src/edit.tsx +85 -0
  59. package/templates/basic/src/hooks.ts +75 -0
  60. package/templates/basic/src/index.tsx +37 -0
  61. package/templates/basic/src/save.tsx +27 -0
  62. package/templates/basic/src/style.scss +42 -0
  63. package/templates/basic/src/types.ts +47 -0
  64. package/templates/basic/src/validators.ts +39 -0
  65. package/templates/basic/tsconfig.json +20 -0
  66. package/templates/basic/webpack.config.js +85 -0
  67. package/templates/full/package.json.mustache +40 -0
  68. package/templates/full/scripts/lib/typia-metadata-core.ts +806 -0
  69. package/templates/full/scripts/sync-types-to-block-json.ts.mustache +25 -0
  70. package/templates/full/src/block.json.mustache +121 -0
  71. package/templates/full/src/edit.tsx.mustache +300 -0
  72. package/templates/full/src/editor.scss.mustache +251 -0
  73. package/templates/full/src/hooks.ts.mustache +140 -0
  74. package/templates/full/src/index.tsx.mustache +27 -0
  75. package/templates/full/src/save.tsx.mustache +39 -0
  76. package/templates/full/src/style.scss.mustache +224 -0
  77. package/templates/full/src/types.ts.mustache +34 -0
  78. package/templates/full/src/validators.ts.mustache +84 -0
  79. package/templates/full/tsconfig.json.mustache +9 -0
  80. package/templates/full/webpack.config.js.mustache +85 -0
  81. package/templates/interactivity/package.json.mustache +41 -0
  82. package/templates/interactivity/scripts/lib/typia-metadata-core.ts +806 -0
  83. package/templates/interactivity/scripts/sync-types-to-block-json.ts.mustache +25 -0
  84. package/templates/interactivity/src/block.json.mustache +75 -0
  85. package/templates/interactivity/src/edit.tsx.mustache +206 -0
  86. package/templates/interactivity/src/interactivity.ts.mustache +183 -0
  87. package/templates/interactivity/src/save.tsx.mustache +87 -0
  88. package/templates/interactivity/src/types.ts.mustache +29 -0
  89. package/templates/interactivity/tsconfig.json.mustache +9 -0
  90. package/templates/interactivity/webpack.config.js.mustache +85 -0
@@ -0,0 +1,25 @@
1
+ import { syncBlockMetadata } from "./lib/typia-metadata-core";
2
+
3
+ async function main() {
4
+ const result = await syncBlockMetadata({
5
+ blockJsonFile: "src/block.json",
6
+ manifestFile: "src/typia.manifest.json",
7
+ sourceTypeName: "{{pascalCase}}Attributes",
8
+ typesFile: "src/types.ts",
9
+ });
10
+
11
+ console.log("✅ block.json and typia.manifest.json were generated from TypeScript types!");
12
+ console.log("📝 Generated attributes:", result.attributeNames);
13
+
14
+ if (result.lossyProjectionWarnings.length > 0) {
15
+ console.warn("⚠️ Some Typia constraints were preserved only in typia.manifest.json:");
16
+ for (const warning of result.lossyProjectionWarnings) {
17
+ console.warn(` - ${warning}`);
18
+ }
19
+ }
20
+ }
21
+
22
+ main().catch((error) => {
23
+ console.error("❌ Type sync failed:", error);
24
+ process.exit(1);
25
+ });
@@ -0,0 +1,75 @@
1
+ {
2
+ "$schema": "https://schemas.wp.org/trunk/block.json",
3
+ "apiVersion": 3,
4
+ "name": "create-block/{{slugKebabCase}}",
5
+ "version": "0.1.0",
6
+ "title": "{{title}}",
7
+ "category": "widgets",
8
+ "icon": "smiley",
9
+ "description": "{{description}}",
10
+ "example": {},
11
+ "supports": {
12
+ "html": false,
13
+ "align": true,
14
+ "anchor": true,
15
+ "className": true,
16
+ "interactivity": true
17
+ },
18
+ "attributes": {
19
+ "content": {
20
+ "type": "string",
21
+ "source": "html",
22
+ "selector": ".{{cssClassName}}__content",
23
+ "default": ""
24
+ },
25
+ "alignment": {
26
+ "type": "string",
27
+ "enum": ["left", "center", "right"],
28
+ "default": "left"
29
+ },
30
+ "isVisible": {
31
+ "type": "boolean",
32
+ "default": true
33
+ },
34
+ "interactiveMode": {
35
+ "type": "string",
36
+ "enum": ["click", "hover", "auto"],
37
+ "default": "click"
38
+ },
39
+ "animation": {
40
+ "type": "string",
41
+ "enum": ["none", "bounce", "pulse", "shake", "flip"],
42
+ "default": "none"
43
+ },
44
+ "clickCount": {
45
+ "type": "number",
46
+ "default": 0
47
+ },
48
+ "isAnimating": {
49
+ "type": "boolean",
50
+ "default": false
51
+ },
52
+ "showCounter": {
53
+ "type": "boolean",
54
+ "default": true
55
+ },
56
+ "maxClicks": {
57
+ "type": "number",
58
+ "default": 10
59
+ },
60
+ "autoPlayInterval": {
61
+ "type": "number",
62
+ "default": 0
63
+ },
64
+ "uniqueId": {
65
+ "type": "string",
66
+ "default": ""
67
+ }
68
+ },
69
+ "textdomain": "{{slugSnakeCase}}",
70
+ "editorScript": "file:./index.js",
71
+ "editorStyle": "file:./index.css",
72
+ "style": "file:./style-index.css",
73
+ "viewScript": "file:./view.js",
74
+ "viewScriptModule": "file:./interactivity.js"
75
+ }
@@ -0,0 +1,206 @@
1
+ import { __ } from '@wordpress/i18n';
2
+ import { useBlockProps, InspectorControls, RichText, BlockControls, AlignmentToolbar } from '@wordpress/block-editor';
3
+ import { PanelBody, ToggleControl, SelectControl, RangeControl, TextControl, Button, Notice } from '@wordpress/components';
4
+ import { useState, useEffect } from '@wordpress/element';
5
+ import type { {{pascalCase}}Attributes } from './types';
6
+
7
+ export default function Edit({ attributes, setAttributes, isSelected }: {
8
+ attributes: {{pascalCase}}Attributes;
9
+ setAttributes: (attrs: Partial<{{pascalCase}}Attributes>) => void;
10
+ isSelected: boolean;
11
+ }) {
12
+ const [isPreviewing, setIsPreviewing] = useState(false);
13
+
14
+ const blockProps = useBlockProps({
15
+ className: `wp-block-{{slugKebabCase}} wp-block-{{slugKebabCase}}--${attributes.interactiveMode}`,
16
+ 'data-wp-interactive': '{{slugKebabCase}}',
17
+ 'data-wp-context': JSON.stringify({
18
+ clicks: attributes.clickCount,
19
+ isAnimating: attributes.isAnimating,
20
+ isVisible: attributes.isVisible,
21
+ lastInteraction: '',
22
+ animation: attributes.animation,
23
+ interactiveMode: attributes.interactiveMode,
24
+ maxClicks: attributes.maxClicks,
25
+ autoPlayInterval: attributes.autoPlayInterval
26
+ })
27
+ });
28
+
29
+ const resetCounter = () => {
30
+ setAttributes({ clickCount: 0, isAnimating: false });
31
+ };
32
+
33
+ const testAnimation = () => {
34
+ setAttributes({ isAnimating: true });
35
+ setTimeout(() => {
36
+ setAttributes({ isAnimating: false });
37
+ }, 1000);
38
+ };
39
+
40
+ return (
41
+ <>
42
+ <BlockControls>
43
+ <AlignmentToolbar
44
+ value={attributes.alignment}
45
+ onChange={(value) => setAttributes({ alignment: value || 'left' })}
46
+ />
47
+ </BlockControls>
48
+
49
+ <InspectorControls>
50
+ <PanelBody title={__('Interactive Settings', '{{slugSnakeCase}}')}>
51
+ <SelectControl
52
+ label={__('Interactive Mode', '{{slugSnakeCase}}')}
53
+ value={attributes.interactiveMode}
54
+ options={[
55
+ { label: __('Click', '{{slugSnakeCase}}'), value: 'click' },
56
+ { label: __('Hover', '{{slugSnakeCase}}'), value: 'hover' },
57
+ { label: __('Auto Play', '{{slugSnakeCase}}'), value: 'auto' }
58
+ ]}
59
+ onChange={(value) => setAttributes({ interactiveMode: value })}
60
+ />
61
+
62
+ <SelectControl
63
+ label={__('Animation', '{{slugSnakeCase}}')}
64
+ value={attributes.animation}
65
+ options={[
66
+ { label: __('None', '{{slugSnakeCase}}'), value: 'none' },
67
+ { label: __('Bounce', '{{slugSnakeCase}}'), value: 'bounce' },
68
+ { label: __('Pulse', '{{slugSnakeCase}}'), value: 'pulse' },
69
+ { label: __('Shake', '{{slugSnakeCase}}'), value: 'shake' },
70
+ { label: __('Flip', '{{slugSnakeCase}}'), value: 'flip' }
71
+ ]}
72
+ onChange={(value) => setAttributes({ animation: value })}
73
+ />
74
+
75
+ <ToggleControl
76
+ label={__('Show Counter', '{{slugSnakeCase}}')}
77
+ checked={attributes.showCounter}
78
+ onChange={(value) => setAttributes({ showCounter: value })}
79
+ />
80
+
81
+ <ToggleControl
82
+ label={__('Visible', '{{slugSnakeCase}}')}
83
+ checked={attributes.isVisible}
84
+ onChange={(value) => setAttributes({ isVisible: value })}
85
+ />
86
+ </PanelBody>
87
+
88
+ <PanelBody title={__('Counter Settings', '{{slugSnakeCase}}')}>
89
+ <RangeControl
90
+ label={__('Max Clicks', '{{slugSnakeCase}}')}
91
+ value={attributes.maxClicks}
92
+ onChange={(value) => setAttributes({ maxClicks: value })}
93
+ min={0}
94
+ max={100}
95
+ help={__('Set to 0 for unlimited clicks', '{{slugSnakeCase}}')}
96
+ />
97
+
98
+ {attributes.interactiveMode === 'auto' && (
99
+ <RangeControl
100
+ label={__('Auto Play Interval (ms)', '{{slugSnakeCase}}')}
101
+ value={attributes.autoPlayInterval}
102
+ onChange={(value) => setAttributes({ autoPlayInterval: value })}
103
+ min={100}
104
+ max={5000}
105
+ step={100}
106
+ help={__('Interval between automatic animations', '{{slugSnakeCase}}')}
107
+ />
108
+ )}
109
+
110
+ <div style={{ display: 'flex', gap: '8px', marginTop: '16px' }}>
111
+ <Button
112
+ variant="secondary"
113
+ onClick={resetCounter}
114
+ isSmall
115
+ >
116
+ {__('Reset Counter', '{{slugSnakeCase}}')}
117
+ </Button>
118
+ <Button
119
+ variant="secondary"
120
+ onClick={testAnimation}
121
+ isSmall
122
+ >
123
+ {__('Test Animation', '{{slugSnakeCase}}')}
124
+ </Button>
125
+ </div>
126
+
127
+ <TextControl
128
+ label={__('Unique ID', '{{slugSnakeCase}}')}
129
+ value={attributes.uniqueId}
130
+ onChange={(value) => setAttributes({ uniqueId: value })}
131
+ help={__('Optional unique identifier for this block instance', '{{slugSnakeCase}}')}
132
+ />
133
+ </PanelBody>
134
+
135
+ {isSelected && (
136
+ <PanelBody title={__('Preview', '{{slugSnakeCase}}')}>
137
+ <ToggleControl
138
+ label={__('Enable Preview Mode', '{{slugSnakeCase}}')}
139
+ checked={isPreviewing}
140
+ onChange={setIsPreviewing}
141
+ help={__('Test interactions in the editor', '{{slugSnakeCase}}')}
142
+ />
143
+
144
+ {attributes.clickCount > 0 && (
145
+ <Notice status="info" isDismissible={false}>
146
+ {__('Current clicks:', '{{slugSnakeCase}}')} {attributes.clickCount}
147
+ {attributes.maxClicks > 0 && ` / ${attributes.maxClicks}`}
148
+ </Notice>
149
+ )}
150
+ </PanelBody>
151
+ )}
152
+ </InspectorControls>
153
+
154
+ <div {...blockProps}>
155
+ <div
156
+ className={`{{cssClassName}}__content ${attributes.isAnimating ? 'is-animating' : ''}`}
157
+ style={{ textAlign: attributes.alignment }}
158
+ data-wp-on--click={isPreviewing ? 'actions.handleClick' : undefined}
159
+ data-wp-on--mouseenter={isPreviewing && attributes.interactiveMode === 'hover' ? 'actions.handleMouseEnter' : undefined}
160
+ data-wp-on--mouseleave={isPreviewing && attributes.interactiveMode === 'hover' ? 'actions.handleMouseLeave' : undefined}
161
+ >
162
+ <RichText
163
+ tagName="p"
164
+ value={attributes.content}
165
+ onChange={(value) => setAttributes({ content: value })}
166
+ placeholder={__('{{title}} – click me to interact!', '{{slugSnakeCase}}')}
167
+ withoutInteractiveFormatting
168
+ />
169
+
170
+ {attributes.showCounter && (
171
+ <div className="{{cssClassName}}__counter">
172
+ <span className="{{cssClassName}}__counter-label">
173
+ {__('Clicks:', '{{slugSnakeCase}}')}
174
+ </span>
175
+ <span
176
+ className="{{cssClassName}}__counter-value"
177
+ data-wp-text="state.clicks"
178
+ >
179
+ {attributes.clickCount}
180
+ </span>
181
+ </div>
182
+ )}
183
+
184
+ {attributes.maxClicks > 0 && (
185
+ <div className="{{cssClassName}}__progress">
186
+ <div
187
+ className="{{cssClassName}}__progress-bar"
188
+ style={{ width: `${(attributes.clickCount / attributes.maxClicks) * 100}%` }}
189
+ data-wp-style--width="state.progress + '%'"
190
+ />
191
+ </div>
192
+ )}
193
+
194
+ {attributes.animation !== 'none' && (
195
+ <div
196
+ className={`{{cssClassName}}__animation ${attributes.isAnimating ? 'is-active' : ''}`}
197
+ data-wp-class="is-active"
198
+ >
199
+ {attributes.animation}
200
+ </div>
201
+ )}
202
+ </div>
203
+ </div>
204
+ </>
205
+ );
206
+ }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * WordPress Interactivity API implementation for {{title}} block
3
+ */
4
+ import { store, getContext, getElement } from '@wordpress/interactivity';
5
+
6
+ // Store configuration
7
+ store('{{slugKebabCase}}', {
8
+ // State - reactive data that updates the UI
9
+ state: {
10
+ get clicks() {
11
+ return getContext().clicks;
12
+ },
13
+ get isAnimating() {
14
+ return getContext().isAnimating;
15
+ },
16
+ get isVisible() {
17
+ return getContext().isVisible;
18
+ },
19
+ get animationClass() {
20
+ const context = getContext();
21
+ if (!context.isAnimating) return '';
22
+
23
+ switch (context.animation) {
24
+ case 'bounce':
25
+ return 'animate-bounce';
26
+ case 'pulse':
27
+ return 'animate-pulse';
28
+ case 'shake':
29
+ return 'animate-shake';
30
+ case 'flip':
31
+ return 'animate-flip';
32
+ default:
33
+ return '';
34
+ }
35
+ },
36
+ get progress() {
37
+ const context = getContext();
38
+ return context.maxClicks > 0 ? (context.clicks / context.maxClicks) * 100 : 0;
39
+ },
40
+ get isComplete() {
41
+ const context = getContext();
42
+ return context.clicks >= context.maxClicks && context.maxClicks > 0;
43
+ }
44
+ },
45
+
46
+ // Actions - user interactions
47
+ actions: {
48
+ // Handle block click
49
+ handleClick: () => {
50
+ const context = getContext();
51
+ const { element } = getElement();
52
+
53
+ // Increment click counter
54
+ context.clicks += 1;
55
+ context.lastInteraction = 'click';
56
+
57
+ // Trigger animation
58
+ if (context.animation !== 'none') {
59
+ context.isAnimating = true;
60
+ setTimeout(() => {
61
+ context.isAnimating = false;
62
+ }, 1000);
63
+ }
64
+
65
+ // Emit custom event
66
+ element.dispatchEvent(new CustomEvent('{{slugKebabCase}}:click', {
67
+ detail: { clicks: context.clicks }
68
+ }));
69
+
70
+ // Check if max clicks reached
71
+ if (context.maxClicks > 0 && context.clicks >= context.maxClicks) {
72
+ element.dispatchEvent(new CustomEvent('{{slugKebabCase}}:complete', {
73
+ detail: { totalClicks: context.clicks }
74
+ }));
75
+ }
76
+ },
77
+
78
+ // Handle hover events
79
+ handleMouseEnter: () => {
80
+ const context = getContext();
81
+ if (context.interactiveMode === 'hover') {
82
+ context.isAnimating = true;
83
+ context.lastInteraction = 'hover';
84
+ }
85
+ },
86
+
87
+ handleMouseLeave: () => {
88
+ const context = getContext();
89
+ if (context.interactiveMode === 'hover') {
90
+ context.isAnimating = false;
91
+ }
92
+ },
93
+
94
+ // Toggle visibility
95
+ toggleVisibility: () => {
96
+ const context = getContext();
97
+ context.isVisible = !context.isVisible;
98
+ },
99
+
100
+ // Reset counter
101
+ reset: () => {
102
+ const context = getContext();
103
+ context.clicks = 0;
104
+ context.isAnimating = false;
105
+ context.lastInteraction = 'reset';
106
+ },
107
+
108
+ // Start/stop auto play
109
+ toggleAutoPlay: () => {
110
+ const context = getContext();
111
+ if (context.autoPlayInterval > 0) {
112
+ if (context.autoPlayTimer) {
113
+ clearInterval(context.autoPlayTimer);
114
+ context.autoPlayTimer = null;
115
+ } else {
116
+ context.autoPlayTimer = setInterval(() => {
117
+ context.clicks += 1;
118
+ context.isAnimating = true;
119
+ setTimeout(() => {
120
+ context.isAnimating = false;
121
+ }, 500);
122
+ }, context.autoPlayInterval);
123
+ }
124
+ }
125
+ }
126
+ },
127
+
128
+ // Callbacks - lifecycle hooks
129
+ callbacks: {
130
+ // Initialize on mount
131
+ onInit: () => {
132
+ const context = getContext();
133
+ const { element } = getElement();
134
+
135
+ // Set initial state from attributes
136
+ context.clicks = parseInt(element.dataset.clicks || '0');
137
+ context.isAnimating = element.dataset.isAnimating === 'true';
138
+ context.isVisible = element.dataset.isVisible !== 'false';
139
+
140
+ // Set up event listeners
141
+ element.addEventListener('{{slugKebabCase}}:external-trigger', (event: any) => {
142
+ context.clicks = event.detail.clicks || context.clicks + 1;
143
+ });
144
+ },
145
+
146
+ // Log interactions
147
+ onInteraction: () => {
148
+ const context = getContext();
149
+ console.log(`{{slugKebabCase}} interaction:`, {
150
+ clicks: context.clicks,
151
+ type: context.lastInteraction,
152
+ timestamp: new Date().toISOString()
153
+ });
154
+ },
155
+
156
+ // Cleanup on unmount
157
+ onDestroy: () => {
158
+ const context = getContext();
159
+ if (context.autoPlayTimer) {
160
+ clearInterval(context.autoPlayTimer);
161
+ }
162
+ }
163
+ }
164
+ });
165
+
166
+ // Type definitions for TypeScript
167
+ declare global {
168
+ namespace WordPress {
169
+ namespace Interactivity {
170
+ interface {{pascalCase}}Context {
171
+ clicks: number;
172
+ isAnimating: boolean;
173
+ isVisible: boolean;
174
+ lastInteraction: string;
175
+ animation: string;
176
+ interactiveMode: string;
177
+ maxClicks: number;
178
+ autoPlayInterval: number;
179
+ autoPlayTimer?: number;
180
+ }
181
+ }
182
+ }
183
+ }
@@ -0,0 +1,87 @@
1
+ import { useBlockProps, RichText } from '@wordpress/block-editor';
2
+ import type { {{pascalCase}}Attributes } from './types';
3
+
4
+ export default function Save({ attributes }: { attributes: {{pascalCase}}Attributes }) {
5
+ const blockProps = useBlockProps.save({
6
+ className: `wp-block-{{slugKebabCase}} wp-block-{{slugKebabCase}}--${attributes.interactiveMode}`,
7
+ 'data-wp-interactive': '{{slugKebabCase}}',
8
+ 'data-wp-context': JSON.stringify({
9
+ clicks: attributes.clickCount,
10
+ isAnimating: attributes.isAnimating,
11
+ isVisible: attributes.isVisible,
12
+ lastInteraction: '',
13
+ animation: attributes.animation,
14
+ interactiveMode: attributes.interactiveMode,
15
+ maxClicks: attributes.maxClicks,
16
+ autoPlayInterval: attributes.autoPlayInterval
17
+ }),
18
+ 'data-clicks': attributes.clickCount,
19
+ 'data-is-animating': attributes.isAnimating,
20
+ 'data-is-visible': attributes.isVisible,
21
+ 'data-unique-id': attributes.uniqueId
22
+ });
23
+
24
+ return (
25
+ <div {...blockProps}>
26
+ <div
27
+ className={`{{cssClassName}}__content ${attributes.isAnimating ? 'is-animating' : ''}`}
28
+ style={{ textAlign: attributes.alignment }}
29
+ data-wp-on--click="actions.handleClick"
30
+ data-wp-on--mouseenter={attributes.interactiveMode === 'hover' ? 'actions.handleMouseEnter' : undefined}
31
+ data-wp-on--mouseleave={attributes.interactiveMode === 'hover' ? 'actions.handleMouseLeave' : undefined}
32
+ data-wp-bind--hidden="!state.isVisible"
33
+ >
34
+ <RichText.Content
35
+ tagName="p"
36
+ value={attributes.content}
37
+ className="{{cssClassName}}__text"
38
+ />
39
+
40
+ {attributes.showCounter && (
41
+ <div className="{{cssClassName}}__counter">
42
+ <span className="{{cssClassName}}__counter-label">
43
+ Clicks:
44
+ </span>
45
+ <span
46
+ className="{{cssClassName}}__counter-value"
47
+ data-wp-text="state.clicks"
48
+ >
49
+ {attributes.clickCount}
50
+ </span>
51
+ </div>
52
+ )}
53
+
54
+ {attributes.maxClicks > 0 && (
55
+ <div className="{{cssClassName}}__progress">
56
+ <div
57
+ className="{{cssClassName}}__progress-bar"
58
+ data-wp-style--width="state.progress + '%'"
59
+ />
60
+ </div>
61
+ )}
62
+
63
+ <div
64
+ className={`{{cssClassName}}__animation ${attributes.animation}`}
65
+ data-wp-class="is-active"
66
+ />
67
+
68
+ {attributes.maxClicks > 0 && (
69
+ <div
70
+ className="{{cssClassName}}__completion"
71
+ data-wp-bind--hidden="!state.isComplete"
72
+ >
73
+ 🎉 Complete!
74
+ </div>
75
+ )}
76
+
77
+ <button
78
+ className="{{cssClassName}}__reset"
79
+ data-wp-on--click="actions.reset"
80
+ aria-label="Reset counter"
81
+ >
82
+
83
+ </button>
84
+ </div>
85
+ </div>
86
+ );
87
+ }
@@ -0,0 +1,29 @@
1
+ import { tags } from "typia";
2
+
3
+ export interface {{pascalCase}}Attributes {
4
+ content: string & tags.MinLength<1> & tags.MaxLength<1000> & tags.Default<"">;
5
+ alignment?: ('left' | 'center' | 'right') & tags.Default<"left">;
6
+ isVisible?: boolean & tags.Default<true>;
7
+ interactiveMode?: ('click' | 'hover' | 'auto') & tags.Default<"click">;
8
+ animation?: ('none' | 'bounce' | 'pulse' | 'shake' | 'flip') & tags.Default<"none">;
9
+ clickCount?: number & tags.Default<0>;
10
+ isAnimating?: boolean & tags.Default<false>;
11
+ showCounter?: boolean & tags.Default<true>;
12
+ maxClicks?: number & tags.Default<10>;
13
+ autoPlayInterval?: number & tags.Default<0>;
14
+ uniqueId?: string & tags.Default<"">;
15
+ }
16
+
17
+ export interface {{pascalCase}}Context {
18
+ clicks: number;
19
+ isAnimating: boolean;
20
+ isVisible: boolean;
21
+ lastInteraction: string;
22
+ animationClass: string;
23
+ }
24
+
25
+ export type {{pascalCase}}ValidationResult = {
26
+ isValid: boolean;
27
+ errors: string[];
28
+ data?: {{pascalCase}}Attributes;
29
+ };
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "@wordpress/scripts/config/tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "declarationDir": "build/types"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["build/**/*"]
9
+ }