@wp-typia/project-tools 0.21.0 → 0.22.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.
- package/dist/runtime/ai-feature-capability.js +2 -33
- package/dist/runtime/built-in-block-artifact-types.js +11 -0
- package/dist/runtime/built-in-block-code-artifacts.js +5 -1
- package/dist/runtime/built-in-block-code-templates/interactivity.d.ts +4 -3
- package/dist/runtime/built-in-block-code-templates/interactivity.js +259 -100
- package/dist/runtime/built-in-block-code-templates.d.ts +1 -1
- package/dist/runtime/built-in-block-code-templates.js +1 -1
- package/dist/runtime/cli-add-shared.d.ts +4 -3
- package/dist/runtime/cli-add-shared.js +5 -2
- package/dist/runtime/cli-add-workspace-ability.js +3 -4
- package/dist/runtime/cli-add-workspace-admin-view.d.ts +2 -0
- package/dist/runtime/cli-add-workspace-admin-view.js +560 -31
- package/dist/runtime/cli-doctor-workspace.js +5 -4
- package/dist/runtime/cli-help.js +5 -2
- package/dist/runtime/external-layer-selection.d.ts +8 -2
- package/dist/runtime/external-layer-selection.js +3 -4
- package/dist/runtime/package-versions.d.ts +13 -0
- package/dist/runtime/package-versions.js +13 -0
- package/dist/runtime/scaffold-compatibility.d.ts +2 -2
- package/dist/runtime/scaffold-compatibility.js +22 -48
- package/dist/runtime/version-floor.d.ts +26 -0
- package/dist/runtime/version-floor.js +56 -0
- package/dist/runtime/workspace-inventory.d.ts +2 -1
- package/package.json +3 -2
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Declares whether a generated feature is optional or required at runtime. */
|
|
2
|
+
import { pickHigherVersionFloor } from './version-floor.js';
|
|
1
3
|
/** Canonical registry of AI-related features supported by wp-typia today. */
|
|
2
4
|
export const AI_FEATURE_DEFINITIONS = {
|
|
3
5
|
wordpressAiClient: {
|
|
@@ -62,39 +64,6 @@ const DEFAULT_AI_FEATURE_REGISTRY = Object.values(AI_FEATURE_DEFINITIONS).reduce
|
|
|
62
64
|
accumulator[definition.id] = definition;
|
|
63
65
|
return accumulator;
|
|
64
66
|
}, {});
|
|
65
|
-
function parseVersionFloorParts(value) {
|
|
66
|
-
return value.split('.').map((part, index) => {
|
|
67
|
-
if (!/^\d+$/.test(part)) {
|
|
68
|
-
throw new Error(`parseVersionFloorParts received an invalid version floor "${value}" at segment ${index + 1}.`);
|
|
69
|
-
}
|
|
70
|
-
return Number.parseInt(part, 10);
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
function compareVersionFloors(left, right) {
|
|
74
|
-
const leftParts = parseVersionFloorParts(left);
|
|
75
|
-
const rightParts = parseVersionFloorParts(right);
|
|
76
|
-
const length = Math.max(leftParts.length, rightParts.length);
|
|
77
|
-
for (let index = 0; index < length; index += 1) {
|
|
78
|
-
const leftValue = leftParts[index] ?? 0;
|
|
79
|
-
const rightValue = rightParts[index] ?? 0;
|
|
80
|
-
if (leftValue > rightValue) {
|
|
81
|
-
return 1;
|
|
82
|
-
}
|
|
83
|
-
if (leftValue < rightValue) {
|
|
84
|
-
return -1;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return 0;
|
|
88
|
-
}
|
|
89
|
-
function pickHigherVersionFloor(current, candidate) {
|
|
90
|
-
if (!candidate) {
|
|
91
|
-
return current;
|
|
92
|
-
}
|
|
93
|
-
if (!current) {
|
|
94
|
-
return candidate;
|
|
95
|
-
}
|
|
96
|
-
return compareVersionFloors(current, candidate) >= 0 ? current : candidate;
|
|
97
|
-
}
|
|
98
67
|
function normalizeSelections(selections) {
|
|
99
68
|
const normalized = new Map();
|
|
100
69
|
for (const selection of selections) {
|
|
@@ -118,6 +118,17 @@ export function buildInteractivityTypesSource(variables, attributes) {
|
|
|
118
118
|
],
|
|
119
119
|
name: `${variables.pascalCase}Context`,
|
|
120
120
|
},
|
|
121
|
+
{
|
|
122
|
+
members: [
|
|
123
|
+
{ name: "clicks", typeExpression: "number" },
|
|
124
|
+
{ name: "isAnimating", typeExpression: "boolean" },
|
|
125
|
+
{ name: "isVisible", typeExpression: "boolean" },
|
|
126
|
+
{ name: "progress", typeExpression: "number" },
|
|
127
|
+
{ name: "clampedClicks", typeExpression: "number" },
|
|
128
|
+
{ name: "isComplete", typeExpression: "boolean" },
|
|
129
|
+
],
|
|
130
|
+
name: `${variables.pascalCase}State`,
|
|
131
|
+
},
|
|
121
132
|
],
|
|
122
133
|
typeAliases: [
|
|
123
134
|
{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildBuiltInNonTsArtifacts } from "./built-in-block-non-ts-artifacts.js";
|
|
2
|
-
import { BASIC_EDIT_TEMPLATE, BASIC_INDEX_TEMPLATE, BASIC_SAVE_TEMPLATE, BASIC_VALIDATORS_TEMPLATE, BLOCK_METADATA_WRAPPER_TEMPLATE, COMPOUND_CHILD_EDIT_TEMPLATE, COMPOUND_CHILD_INDEX_TEMPLATE, COMPOUND_CHILD_SAVE_TEMPLATE, COMPOUND_CHILD_VALIDATORS_TEMPLATE, COMPOUND_CHILDREN_TEMPLATE, COMPOUND_LOCAL_HOOKS_TEMPLATE, COMPOUND_PARENT_EDIT_TEMPLATE, COMPOUND_PARENT_INDEX_TEMPLATE, COMPOUND_PARENT_SAVE_TEMPLATE, COMPOUND_PARENT_VALIDATORS_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_EDIT_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_INTERACTIVITY_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_SAVE_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_VALIDATORS_TEMPLATE, INTERACTIVITY_EDIT_TEMPLATE, INTERACTIVITY_INDEX_TEMPLATE, INTERACTIVITY_SAVE_TEMPLATE, INTERACTIVITY_SCRIPT_TEMPLATE, INTERACTIVITY_VALIDATORS_TEMPLATE, MANIFEST_DEFAULTS_DOCUMENT_WRAPPER_TEMPLATE, MANIFEST_DOCUMENT_WRAPPER_TEMPLATE, PERSISTENCE_EDIT_TEMPLATE, PERSISTENCE_INDEX_TEMPLATE, PERSISTENCE_INTERACTIVITY_TEMPLATE, PERSISTENCE_SAVE_TEMPLATE, PERSISTENCE_VALIDATORS_TEMPLATE, QUERY_LOOP_INDEX_TEMPLATE, SHARED_HOOKS_TEMPLATE, } from "./built-in-block-code-templates.js";
|
|
2
|
+
import { BASIC_EDIT_TEMPLATE, BASIC_INDEX_TEMPLATE, BASIC_SAVE_TEMPLATE, BASIC_VALIDATORS_TEMPLATE, BLOCK_METADATA_WRAPPER_TEMPLATE, COMPOUND_CHILD_EDIT_TEMPLATE, COMPOUND_CHILD_INDEX_TEMPLATE, COMPOUND_CHILD_SAVE_TEMPLATE, COMPOUND_CHILD_VALIDATORS_TEMPLATE, COMPOUND_CHILDREN_TEMPLATE, COMPOUND_LOCAL_HOOKS_TEMPLATE, COMPOUND_PARENT_EDIT_TEMPLATE, COMPOUND_PARENT_INDEX_TEMPLATE, COMPOUND_PARENT_SAVE_TEMPLATE, COMPOUND_PARENT_VALIDATORS_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_EDIT_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_INTERACTIVITY_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_SAVE_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_VALIDATORS_TEMPLATE, INTERACTIVITY_EDIT_TEMPLATE, INTERACTIVITY_INDEX_TEMPLATE, INTERACTIVITY_SAVE_TEMPLATE, INTERACTIVITY_SCRIPT_TEMPLATE, INTERACTIVITY_STORE_TEMPLATE, INTERACTIVITY_VALIDATORS_TEMPLATE, MANIFEST_DEFAULTS_DOCUMENT_WRAPPER_TEMPLATE, MANIFEST_DOCUMENT_WRAPPER_TEMPLATE, PERSISTENCE_EDIT_TEMPLATE, PERSISTENCE_INDEX_TEMPLATE, PERSISTENCE_INTERACTIVITY_TEMPLATE, PERSISTENCE_SAVE_TEMPLATE, PERSISTENCE_VALIDATORS_TEMPLATE, QUERY_LOOP_INDEX_TEMPLATE, SHARED_HOOKS_TEMPLATE, } from "./built-in-block-code-templates.js";
|
|
3
3
|
import { getScaffoldTemplateVariableGroups } from "./scaffold-template-variable-groups.js";
|
|
4
4
|
import { renderMustacheTemplateString } from "./template-render.js";
|
|
5
5
|
function renderCodeTemplate(template, variables) {
|
|
@@ -88,6 +88,10 @@ function buildInteractivityCodeArtifacts(variables) {
|
|
|
88
88
|
relativePath: "src/interactivity.ts",
|
|
89
89
|
template: INTERACTIVITY_SCRIPT_TEMPLATE,
|
|
90
90
|
},
|
|
91
|
+
{
|
|
92
|
+
relativePath: "src/interactivity-store.ts",
|
|
93
|
+
template: INTERACTIVITY_STORE_TEMPLATE,
|
|
94
|
+
},
|
|
91
95
|
{
|
|
92
96
|
relativePath: "src/validators.ts",
|
|
93
97
|
template: INTERACTIVITY_VALIDATORS_TEMPLATE,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
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':
|
|
2
|
-
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':
|
|
1
|
+
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 { {{slugCamelCase}}Store } from './interactivity-store';\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': {{slugCamelCase}}Store.directive.interactive,\n 'data-wp-context': JSON.stringify(\n {{slugCamelCase}}Store.createContext({\n clicks: clickCount,\n isAnimating,\n isVisible,\n animation,\n maxClicks,\n })\n )\n });\n const previewContentStyle = { textAlign: alignmentValue };\n const progressBarStyle = { width: `${(clickCount / maxClicks) * 100}%` };\n const clicksDirective = {{slugCamelCase}}Store.directive.state('clicks');\n const isAnimatingDirective = {{slugCamelCase}}Store.directive.state('isAnimating');\n const progressDirective = {{slugCamelCase}}Store.directive.state('progress') + \" + '%'\";\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 ? {{slugCamelCase}}Store.directive.action('handleClick') : undefined}\n data-wp-on--mouseenter={isPreviewing && interactiveMode === 'hover' ? {{slugCamelCase}}Store.directive.action('handleMouseEnter') : undefined}\n data-wp-on--mouseleave={isPreviewing && interactiveMode === 'hover' ? {{slugCamelCase}}Store.directive.action('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={clicksDirective}\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={progressDirective}\n />\n </div>\n )}\n\n {animation !== 'none' && (\n <div\n className={`{{cssClassName}}__animation ${isAnimating ? 'is-active' : ''}`}\n data-wp-class--is-active={isAnimatingDirective}\n >\n {animation}\n </div>\n )}\n </div>\n </div>\n </>\n );\n}\n";
|
|
2
|
+
export declare const INTERACTIVITY_SAVE_TEMPLATE = "import { useBlockProps, RichText } from '@wordpress/block-editor';\nimport { __ } from '@wordpress/i18n';\nimport { {{slugCamelCase}}Store } from './interactivity-store';\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 clickActionDirective = {{slugCamelCase}}Store.directive.action('handleClick');\n const visibilityHiddenDirective = {{slugCamelCase}}Store.directive.negate(\n {{slugCamelCase}}Store.directive.state('isVisible')\n );\n const clicksDirective = {{slugCamelCase}}Store.directive.state('clicks');\n const clampedClicksDirective = {{slugCamelCase}}Store.directive.state('clampedClicks');\n const isAnimatingDirective = {{slugCamelCase}}Store.directive.state('isAnimating');\n const completionHiddenDirective = {{slugCamelCase}}Store.directive.negate(\n {{slugCamelCase}}Store.directive.state('isComplete')\n );\n const resetActionDirective = {{slugCamelCase}}Store.directive.action('reset');\n const blockProps = useBlockProps.save({\n className: `{{cssClassName}} {{cssClassName}}--${interactiveMode}`,\n 'data-wp-interactive': {{slugCamelCase}}Store.directive.interactive,\n 'data-wp-context': JSON.stringify(\n {{slugCamelCase}}Store.createContext({\n clicks: clickCount,\n isAnimating,\n isVisible,\n animation,\n maxClicks,\n })\n )\n });\n const progressDirective = {{slugCamelCase}}Store.directive.state('progress') + \" + '%'\";\n\n return (\n <div {...blockProps}>\n <div\n className={`{{cssClassName}}__content ${isAnimating ? 'is-animating' : ''}`}\n style={contentStyle}\n data-wp-on--click={clickActionDirective}\n data-wp-on--mouseenter={interactiveMode === 'hover' ? {{slugCamelCase}}Store.directive.action('handleMouseEnter') : undefined}\n data-wp-on--mouseleave={interactiveMode === 'hover' ? {{slugCamelCase}}Store.directive.action('handleMouseLeave') : undefined}\n data-wp-bind--hidden={visibilityHiddenDirective}\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={clicksDirective}\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={clampedClicksDirective}\n data-wp-style--width={progressDirective}\n />\n </div>\n )}\n\n <div\n className={`{{cssClassName}}__animation ${animation}`}\n aria-hidden=\"true\"\n data-wp-class--is-active={isAnimatingDirective}\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={completionHiddenDirective}\n >\n { __( '\uD83C\uDF89 Complete!', '{{textDomain}}' ) }\n </div>\n )}\n\n <button\n className=\"{{cssClassName}}__reset\"\n data-wp-on--click={resetActionDirective}\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";
|
|
3
3
|
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";
|
|
4
|
-
export declare const
|
|
4
|
+
export declare const INTERACTIVITY_STORE_TEMPLATE = "import type {\n {{pascalCase}}Context,\n {{pascalCase}}State,\n} from './types';\n\ntype InteractivityActionShape = object;\ntype InteractivityCallbackShape = object;\ntype InteractivityContextShape = object;\ntype InteractivityStateShape = object;\ntype InteractivityCallable = Function;\ntype InteractivityKey<T extends object> = Extract<keyof T, string>;\ntype InteractivityMethodKey<T extends object> = {\n [Key in InteractivityKey<T>]: T[Key] extends InteractivityCallable ? Key : never;\n}[InteractivityKey<T>];\n\ntype InteractivityDirectivePath<\n Root extends string,\n Key extends string,\n> = `${Root}.${Key}`;\n\ntype NegatedInteractivityDirectivePath<Path extends string> = `!${Path}`;\n\nexport interface TypedInteractivityDirectiveHelpers<\n State extends InteractivityStateShape,\n Context extends InteractivityContextShape,\n Actions extends InteractivityActionShape,\n Callbacks extends InteractivityCallbackShape,\n Namespace extends string,\n> {\n readonly interactive: Namespace;\n action<Key extends InteractivityMethodKey<Actions>>(\n key: Key,\n ): InteractivityDirectivePath<'actions', Key>;\n callback<Key extends InteractivityMethodKey<Callbacks>>(\n key: Key,\n ): InteractivityDirectivePath<'callbacks', Key>;\n state<Key extends InteractivityKey<State>>(\n key: Key,\n ): InteractivityDirectivePath<'state', Key>;\n context<Key extends InteractivityKey<Context>>(\n key: Key,\n ): InteractivityDirectivePath<'context', Key>;\n negate<Path extends string>(\n path: Path,\n ): NegatedInteractivityDirectivePath<Path>;\n}\n\nexport interface TypedInteractivityStore<\n Namespace extends string,\n State extends InteractivityStateShape,\n Context extends InteractivityContextShape,\n Actions extends InteractivityActionShape,\n Callbacks extends InteractivityCallbackShape,\n> {\n readonly namespace: Namespace;\n readonly state: State;\n readonly context: Context;\n readonly actions: Actions;\n readonly callbacks: Callbacks;\n readonly directive: TypedInteractivityDirectiveHelpers<\n State,\n Context,\n Actions,\n Callbacks,\n Namespace\n >;\n createContext(value: Context): Context;\n}\n\nexport function defineInteractivityStore<\n Namespace extends string,\n State extends InteractivityStateShape,\n Context extends InteractivityContextShape,\n Actions extends InteractivityActionShape,\n Callbacks extends InteractivityCallbackShape,\n>(config: {\n readonly namespace: Namespace;\n readonly state: State;\n readonly context: Context;\n readonly actions: Actions;\n readonly callbacks: Callbacks;\n}): TypedInteractivityStore<Namespace, State, Context, Actions, Callbacks> {\n return {\n namespace: config.namespace,\n state: config.state,\n context: config.context,\n actions: config.actions,\n callbacks: config.callbacks,\n directive: {\n interactive: config.namespace,\n action<Key extends InteractivityMethodKey<Actions>>(key: Key) {\n return `actions.${key}` as InteractivityDirectivePath<'actions', Key>;\n },\n callback<Key extends InteractivityMethodKey<Callbacks>>(key: Key) {\n return `callbacks.${key}` as InteractivityDirectivePath<'callbacks', Key>;\n },\n state<Key extends InteractivityKey<State>>(key: Key) {\n return `state.${key}` as InteractivityDirectivePath<'state', Key>;\n },\n context<Key extends InteractivityKey<Context>>(key: Key) {\n return `context.${key}` as InteractivityDirectivePath<'context', Key>;\n },\n negate<Path extends string>(path: Path) {\n return `!${path}` as NegatedInteractivityDirectivePath<Path>;\n },\n },\n createContext(value) {\n return value;\n },\n };\n}\n\ntype InteractivityActionHandler = Function;\n\nexport interface {{pascalCase}}StoreActions {\n handleClick: InteractivityActionHandler;\n handleMouseEnter: InteractivityActionHandler;\n handleMouseLeave: InteractivityActionHandler;\n reset: InteractivityActionHandler;\n}\n\nexport interface {{pascalCase}}StoreCallbacks {}\n\nexport const {{slugCamelCase}}Store = defineInteractivityStore({\n namespace: '{{slugKebabCase}}',\n state: {} as {{pascalCase}}State,\n context: {} as {{pascalCase}}Context,\n actions: {} as {{pascalCase}}StoreActions,\n callbacks: {} as {{pascalCase}}StoreCallbacks,\n});\n";
|
|
5
|
+
export declare const INTERACTIVITY_SCRIPT_TEMPLATE = "/**\n * WordPress Interactivity API implementation for {{title}} block\n */\nimport { store, getContext, getElement, withSyncEvent } from '@wordpress/interactivity';\nimport {\n {{slugCamelCase}}Store,\n type {{pascalCase}}StoreActions,\n} from './interactivity-store';\nimport type { {{pascalCase}}Context, {{pascalCase}}State } from './types';\n\nfunction getBlockContext() {\n return getContext<{{pascalCase}}Context>();\n}\n\nconst actions: {{pascalCase}}StoreActions = {\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\nconst 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} satisfies {{pascalCase}}State;\n\n// Store configuration\nstore({{slugCamelCase}}Store.namespace, {\n // State - reactive data that updates the UI\n state,\n actions,\n callbacks: {{slugCamelCase}}Store.callbacks,\n});\n";
|
|
5
6
|
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";
|