@wp-typia/block-types 0.3.0 → 0.3.2
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/README.md +46 -0
- package/dist/blocks/bindings-codegen.d.ts +13 -0
- package/dist/blocks/bindings-codegen.js +210 -0
- package/dist/blocks/bindings-core.d.ts +116 -0
- package/dist/blocks/bindings-core.js +65 -0
- package/dist/blocks/bindings-diagnostics.d.ts +8 -0
- package/dist/blocks/bindings-diagnostics.js +95 -0
- package/dist/blocks/bindings-manifest.d.ts +12 -0
- package/dist/blocks/bindings-manifest.js +82 -0
- package/dist/blocks/bindings-settings.d.ts +27 -0
- package/dist/blocks/bindings-settings.js +115 -0
- package/dist/blocks/bindings.d.ts +2 -132
- package/dist/blocks/bindings.js +2 -598
- package/dist/blocks/shared/diagnostics.d.ts +13 -0
- package/dist/blocks/shared/diagnostics.js +19 -0
- package/dist/blocks/shared/object-utils.d.ts +2 -0
- package/dist/blocks/shared/object-utils.js +10 -0
- package/dist/blocks/shared/static-registration.d.ts +4 -0
- package/dist/blocks/shared/static-registration.js +32 -0
- package/dist/blocks/supports-diagnostics.d.ts +3 -0
- package/dist/blocks/supports-diagnostics.js +7 -0
- package/dist/blocks/supports-features.d.ts +10 -0
- package/dist/blocks/supports-features.js +53 -0
- package/dist/blocks/supports-manifest.d.ts +4 -0
- package/dist/blocks/supports-manifest.js +97 -0
- package/dist/blocks/supports-settings.d.ts +8 -0
- package/dist/blocks/supports-settings.js +42 -0
- package/dist/blocks/supports.d.ts +8 -14
- package/dist/blocks/supports.js +8 -209
- package/dist/blocks/variations-diagnostics.d.ts +6 -0
- package/dist/blocks/variations-diagnostics.js +123 -0
- package/dist/blocks/variations-manifest.d.ts +5 -0
- package/dist/blocks/variations-manifest.js +17 -0
- package/dist/blocks/variations-settings.d.ts +18 -0
- package/dist/blocks/variations-settings.js +50 -0
- package/dist/blocks/variations.d.ts +6 -3
- package/dist/blocks/variations.js +12 -211
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,6 +108,52 @@ generation. Non-strict mode downgrades them to warnings and recommends guarded
|
|
|
108
108
|
generation. Unknown future keys are guarded by default, or passed through only
|
|
109
109
|
when `allowUnknownFutureKeys` is enabled.
|
|
110
110
|
|
|
111
|
+
## Diagnostic output policy
|
|
112
|
+
|
|
113
|
+
Supports, Variations, and Bindings helpers keep diagnostics structured and
|
|
114
|
+
silent by default. Strict diagnostics still throw grouped errors, but non-strict
|
|
115
|
+
warnings do not write to `console.warn` unless a consumer opts into visible
|
|
116
|
+
output.
|
|
117
|
+
|
|
118
|
+
Use `onDiagnostic` when callers need callback-driven diagnostics for tests,
|
|
119
|
+
custom reporting, or UI integration:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
const diagnostics: Array<{ message: string; severity: 'warning' | 'error' }> =
|
|
123
|
+
[];
|
|
124
|
+
|
|
125
|
+
defineSupports(
|
|
126
|
+
{
|
|
127
|
+
allowedBlocks: true,
|
|
128
|
+
minWordPress: '6.8',
|
|
129
|
+
strict: false,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
onDiagnostic: (diagnostic) => {
|
|
133
|
+
diagnostics.push(diagnostic);
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Use `logger` when callers want formatted warning messages without taking over
|
|
140
|
+
the structured callback path. Passing `console` restores console warning output.
|
|
141
|
+
If both `onDiagnostic` and `logger` are provided, `onDiagnostic` handles the
|
|
142
|
+
diagnostic and the logger is not called.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
defineSupports(
|
|
146
|
+
{
|
|
147
|
+
allowedBlocks: true,
|
|
148
|
+
minWordPress: '6.8',
|
|
149
|
+
strict: false,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
logger: console,
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
```
|
|
156
|
+
|
|
111
157
|
## Validation coverage
|
|
112
158
|
|
|
113
159
|
`@wp-typia/block-types` now validates itself with a mixed strategy that matches
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type DefinedBindingSource } from "./bindings-core.js";
|
|
2
|
+
export interface CreatePhpBindingSourceRegistrationSourceOptions {
|
|
3
|
+
readonly functionName?: string;
|
|
4
|
+
readonly hook?: string;
|
|
5
|
+
readonly includeOpeningTag?: boolean;
|
|
6
|
+
readonly textDomain?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface CreateEditorBindingSourceRegistrationSourceOptions {
|
|
9
|
+
readonly functionName?: string;
|
|
10
|
+
readonly importSource?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function createPhpBindingSourceRegistrationSource(sources: DefinedBindingSource | readonly DefinedBindingSource[], options?: CreatePhpBindingSourceRegistrationSourceOptions): string;
|
|
13
|
+
export declare function createEditorBindingSourceRegistrationSource(sources: DefinedBindingSource | readonly DefinedBindingSource[], options?: CreateEditorBindingSourceRegistrationSourceOptions): string;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { createBindingSourceRegistrationPlan, } from "./bindings-core.js";
|
|
2
|
+
import { normalizeStaticRegistrationValue } from "./shared/static-registration.js";
|
|
3
|
+
function getBindingEvaluation(metadata, feature) {
|
|
4
|
+
return metadata.manifest.evaluations.find((evaluation) => evaluation.area === "blockBindings" && evaluation.feature === feature);
|
|
5
|
+
}
|
|
6
|
+
function shouldGenerateFeature(metadata, feature) {
|
|
7
|
+
const evaluation = getBindingEvaluation(metadata, feature);
|
|
8
|
+
return evaluation?.action === "generate";
|
|
9
|
+
}
|
|
10
|
+
function asSourceList(sources) {
|
|
11
|
+
if (Array.isArray(sources)) {
|
|
12
|
+
return sources;
|
|
13
|
+
}
|
|
14
|
+
return [sources];
|
|
15
|
+
}
|
|
16
|
+
function escapePhpSingleQuotedString(value) {
|
|
17
|
+
return value.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'");
|
|
18
|
+
}
|
|
19
|
+
function phpString(value) {
|
|
20
|
+
return `'${escapePhpSingleQuotedString(value)}'`;
|
|
21
|
+
}
|
|
22
|
+
function phpStringArray(values) {
|
|
23
|
+
return values.length === 0
|
|
24
|
+
? "array()"
|
|
25
|
+
: `array( ${values.map((value) => phpString(value)).join(", ")} )`;
|
|
26
|
+
}
|
|
27
|
+
const PHP_IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
28
|
+
function sanitizePhpIdentifier(value) {
|
|
29
|
+
if (PHP_IDENTIFIER_PATTERN.test(value)) {
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
const sanitized = value.replace(/[^A-Za-z0-9_]/gu, "_").replace(/_+/gu, "_");
|
|
33
|
+
return /^[A-Za-z_]/u.test(sanitized) ? sanitized : `wp_typia_${sanitized}`;
|
|
34
|
+
}
|
|
35
|
+
function createPhpIdentifierHash(value) {
|
|
36
|
+
let hash = 0x811c9dc5;
|
|
37
|
+
for (const character of value) {
|
|
38
|
+
hash ^= character.codePointAt(0) ?? 0;
|
|
39
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
40
|
+
}
|
|
41
|
+
return hash.toString(36);
|
|
42
|
+
}
|
|
43
|
+
function createPhpSourceProperties(source, options) {
|
|
44
|
+
const properties = [];
|
|
45
|
+
if (source.label) {
|
|
46
|
+
const label = options.textDomain
|
|
47
|
+
? `__( ${phpString(source.label)}, ${phpString(options.textDomain)} )`
|
|
48
|
+
: phpString(source.label);
|
|
49
|
+
properties.push(`'label' => ${label}`);
|
|
50
|
+
}
|
|
51
|
+
if (source.usesContext && source.usesContext.length > 0) {
|
|
52
|
+
properties.push(`'uses_context' => ${phpStringArray(source.usesContext)}`);
|
|
53
|
+
}
|
|
54
|
+
if (source.getValueCallback) {
|
|
55
|
+
properties.push(`'get_value_callback' => ${phpString(source.getValueCallback)}`);
|
|
56
|
+
}
|
|
57
|
+
return properties;
|
|
58
|
+
}
|
|
59
|
+
function createPhpBindableAttributeFilterSource(entries, functionName) {
|
|
60
|
+
const lines = [];
|
|
61
|
+
const targets = entries.flatMap((entry) => (entry.source.bindableAttributes ?? []).map((target, index) => ({
|
|
62
|
+
index,
|
|
63
|
+
metadata: entry.metadata,
|
|
64
|
+
sourceName: entry.source.name,
|
|
65
|
+
target,
|
|
66
|
+
})));
|
|
67
|
+
if (targets.length === 0) {
|
|
68
|
+
return lines;
|
|
69
|
+
}
|
|
70
|
+
lines.push("");
|
|
71
|
+
lines.push("if ( function_exists( 'get_block_bindings_supported_attributes' ) ) {");
|
|
72
|
+
for (const [targetIndex, target] of targets.entries()) {
|
|
73
|
+
if (!shouldGenerateFeature(target.metadata, "supportedAttributesFilter")) {
|
|
74
|
+
lines.push(`\t// block_bindings_supported_attributes requires WordPress 6.9+ for ${target.target.blockName}.`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const callbackSeed = [
|
|
78
|
+
functionName,
|
|
79
|
+
target.sourceName,
|
|
80
|
+
target.target.blockName,
|
|
81
|
+
String(target.index),
|
|
82
|
+
String(targetIndex),
|
|
83
|
+
].join("\0");
|
|
84
|
+
const callbackName = sanitizePhpIdentifier(`${functionName}_${target.sourceName}_${target.target.blockName}_${target.index}_${targetIndex}_${createPhpIdentifierHash(callbackSeed)}_supported_attributes`);
|
|
85
|
+
lines.push(`\tadd_filter( ${phpString(`block_bindings_supported_attributes_${target.target.blockName}`)}, ${phpString(callbackName)} );`);
|
|
86
|
+
lines.push(`\tfunction ${callbackName}( $supported_attributes ) {`);
|
|
87
|
+
lines.push(`\t\treturn array_values( array_unique( array_merge( $supported_attributes, ${phpStringArray(target.target.attributes)} ) ) );`);
|
|
88
|
+
lines.push("\t}");
|
|
89
|
+
}
|
|
90
|
+
lines.push("}");
|
|
91
|
+
return lines;
|
|
92
|
+
}
|
|
93
|
+
export function createPhpBindingSourceRegistrationSource(sources, options = {}) {
|
|
94
|
+
const functionName = sanitizePhpIdentifier(options.functionName ?? "wp_typia_register_block_binding_sources");
|
|
95
|
+
const hook = options.hook ?? "init";
|
|
96
|
+
const entries = createBindingSourceRegistrationPlan(asSourceList(sources));
|
|
97
|
+
const lines = [];
|
|
98
|
+
if (options.includeOpeningTag ?? false) {
|
|
99
|
+
lines.push("<?php");
|
|
100
|
+
lines.push("");
|
|
101
|
+
}
|
|
102
|
+
lines.push(`function ${functionName}() {`);
|
|
103
|
+
lines.push("\tif ( ! function_exists( 'register_block_bindings_source' ) ) {");
|
|
104
|
+
lines.push("\t\treturn;");
|
|
105
|
+
lines.push("\t}");
|
|
106
|
+
lines.push("");
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
if (!shouldGenerateFeature(entry.metadata, "serverRegistration")) {
|
|
109
|
+
lines.push(`\t// register_block_bindings_source() requires WordPress 6.5+ for ${entry.source.name}.`);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const properties = createPhpSourceProperties(entry.source, options);
|
|
113
|
+
lines.push(`\tregister_block_bindings_source( ${phpString(entry.source.name)}, array(`);
|
|
114
|
+
for (const property of properties) {
|
|
115
|
+
lines.push(`\t\t${property},`);
|
|
116
|
+
}
|
|
117
|
+
lines.push("\t) );");
|
|
118
|
+
}
|
|
119
|
+
lines.push("}");
|
|
120
|
+
lines.push(`add_action( ${phpString(hook)}, ${phpString(functionName)} );`);
|
|
121
|
+
lines.push(...createPhpBindableAttributeFilterSource(entries, functionName));
|
|
122
|
+
lines.push("");
|
|
123
|
+
return lines.join("\n");
|
|
124
|
+
}
|
|
125
|
+
function createEditorRegistrationEntries(entries) {
|
|
126
|
+
const fields = {};
|
|
127
|
+
const skipped = [];
|
|
128
|
+
const skippedFields = [];
|
|
129
|
+
const sources = [];
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
const editorEvaluation = getBindingEvaluation(entry.metadata, "editorRegistration");
|
|
132
|
+
if (editorEvaluation === undefined) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (editorEvaluation.action !== "generate") {
|
|
136
|
+
skipped.push(entry.source.name);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
sources.push(normalizeStaticRegistrationValue({
|
|
140
|
+
label: entry.source.label,
|
|
141
|
+
name: entry.source.name,
|
|
142
|
+
usesContext: entry.source.usesContext,
|
|
143
|
+
}, `sources.${entry.source.name}`, { description: "binding source" }));
|
|
144
|
+
if ((entry.source.fields?.length ?? 0) === 0) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (!shouldGenerateFeature(entry.metadata, "editorFieldsList")) {
|
|
148
|
+
skippedFields.push(entry.source.name);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
fields[entry.source.name] = normalizeStaticRegistrationValue(entry.source.fields, `fields.${entry.source.name}`, { description: "binding source" });
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
fields,
|
|
155
|
+
skipped,
|
|
156
|
+
skippedFields,
|
|
157
|
+
sources,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
export function createEditorBindingSourceRegistrationSource(sources, options = {}) {
|
|
161
|
+
const importSource = options.importSource ?? "@wordpress/blocks";
|
|
162
|
+
const functionName = options.functionName ?? "registerWpTypiaBindingSources";
|
|
163
|
+
const entries = createBindingSourceRegistrationPlan(asSourceList(sources));
|
|
164
|
+
const registration = createEditorRegistrationEntries(entries);
|
|
165
|
+
if (registration.sources.length === 0) {
|
|
166
|
+
return [
|
|
167
|
+
"// No editor block binding sources were generated.",
|
|
168
|
+
...(registration.skipped.length > 0
|
|
169
|
+
? [
|
|
170
|
+
`// Skipped editor registration for ${registration.skipped.join(", ")}; registerBlockBindingsSource() requires WordPress 6.7+.`,
|
|
171
|
+
]
|
|
172
|
+
: []),
|
|
173
|
+
"",
|
|
174
|
+
].join("\n");
|
|
175
|
+
}
|
|
176
|
+
const serializedSources = JSON.stringify(registration.sources, null, 2);
|
|
177
|
+
const serializedFields = JSON.stringify(registration.fields, null, 2);
|
|
178
|
+
const hasGeneratedFields = Object.keys(registration.fields).length > 0;
|
|
179
|
+
const lines = [
|
|
180
|
+
`import { registerBlockBindingsSource } from ${JSON.stringify(importSource)};`,
|
|
181
|
+
"",
|
|
182
|
+
`const sources = ${serializedSources};`,
|
|
183
|
+
...(hasGeneratedFields ? [`const fieldsBySource = ${serializedFields};`] : []),
|
|
184
|
+
"",
|
|
185
|
+
`export function ${functionName}() {`,
|
|
186
|
+
" if (typeof registerBlockBindingsSource !== \"function\") {",
|
|
187
|
+
" return;",
|
|
188
|
+
" }",
|
|
189
|
+
" for (const source of sources) {",
|
|
190
|
+
...(hasGeneratedFields
|
|
191
|
+
? [
|
|
192
|
+
" const fields = fieldsBySource[source.name];",
|
|
193
|
+
" registerBlockBindingsSource({",
|
|
194
|
+
" ...source,",
|
|
195
|
+
" ...(fields ? { getFieldsList: () => fields } : {}),",
|
|
196
|
+
" });",
|
|
197
|
+
]
|
|
198
|
+
: [" registerBlockBindingsSource(source);"]),
|
|
199
|
+
" }",
|
|
200
|
+
"}",
|
|
201
|
+
];
|
|
202
|
+
if (registration.skipped.length > 0) {
|
|
203
|
+
lines.push("", `// Skipped editor registration for ${registration.skipped.join(", ")}; registerBlockBindingsSource() requires WordPress 6.7+.`);
|
|
204
|
+
}
|
|
205
|
+
if (registration.skippedFields.length > 0) {
|
|
206
|
+
lines.push("", `// Skipped getFieldsList() for ${registration.skippedFields.join(", ")}; getFieldsList() requires WordPress 6.9+.`);
|
|
207
|
+
}
|
|
208
|
+
lines.push("");
|
|
209
|
+
return lines.join("\n");
|
|
210
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { BlockAttributes } from "./registration.js";
|
|
2
|
+
import { type WordPressBlockApiCompatibilityDiagnostic, type WordPressBlockApiCompatibilityFeature, type WordPressBlockApiCompatibilityManifest, type WordPressCompatibilitySettings, type WordPressVersion } from "./compatibility.js";
|
|
3
|
+
import { type DiagnosticLogger } from "./shared/diagnostics.js";
|
|
4
|
+
import { collectBindingSourceCompatibilityFeatures, createBindingSourceCompatibilityManifest } from "./bindings-manifest.js";
|
|
5
|
+
export { collectBindingSourceCompatibilityFeatures, createBindingSourceCompatibilityManifest, };
|
|
6
|
+
export type BindingSourceName = `${string}/${string}`;
|
|
7
|
+
export type BindingSourceArgs = Readonly<Record<string, unknown>>;
|
|
8
|
+
export type BindingFieldType = "array" | "boolean" | "integer" | "number" | "object" | "string";
|
|
9
|
+
export interface BindingSourceField<TArgs extends BindingSourceArgs = BindingSourceArgs> {
|
|
10
|
+
readonly args?: TArgs;
|
|
11
|
+
readonly label: string;
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly type?: BindingFieldType;
|
|
14
|
+
}
|
|
15
|
+
export type BlockBindingAttributeName<TAttributes extends BlockAttributes = BlockAttributes> = Extract<Exclude<keyof TAttributes, "metadata">, string>;
|
|
16
|
+
export interface BindingSourceBindableAttributes<TAttributes extends BlockAttributes = BlockAttributes, TBlockName extends string = string, TAttributesList extends readonly BlockBindingAttributeName<TAttributes>[] = readonly BlockBindingAttributeName<TAttributes>[]> {
|
|
17
|
+
readonly attributes: TAttributesList;
|
|
18
|
+
readonly blockName: TBlockName;
|
|
19
|
+
}
|
|
20
|
+
export interface BindingSourceDefinition<TName extends string = string, TArgs extends BindingSourceArgs = BindingSourceArgs, TFields extends readonly BindingSourceField[] = readonly BindingSourceField[]> {
|
|
21
|
+
readonly args?: TArgs;
|
|
22
|
+
readonly bindableAttributes?: readonly BindingSourceBindableAttributes[];
|
|
23
|
+
readonly fields?: TFields;
|
|
24
|
+
readonly getValueCallback?: string;
|
|
25
|
+
readonly label?: string;
|
|
26
|
+
readonly name: TName;
|
|
27
|
+
readonly usesContext?: readonly string[];
|
|
28
|
+
}
|
|
29
|
+
export interface BindingSourceVersionGates {
|
|
30
|
+
readonly editor?: WordPressVersion;
|
|
31
|
+
readonly fieldsList?: WordPressVersion;
|
|
32
|
+
readonly server?: WordPressVersion;
|
|
33
|
+
readonly supportedAttributesFilter?: WordPressVersion;
|
|
34
|
+
}
|
|
35
|
+
export interface DefineBindingSourceInlineOptions {
|
|
36
|
+
readonly allowUnknownFutureKeys?: boolean;
|
|
37
|
+
readonly editor?: boolean;
|
|
38
|
+
readonly fieldsList?: boolean;
|
|
39
|
+
readonly logger?: DiagnosticLogger<BindingSourceDiagnostic>;
|
|
40
|
+
readonly minVersion?: WordPressVersion;
|
|
41
|
+
readonly minWordPress?: WordPressVersion | BindingSourceVersionGates;
|
|
42
|
+
readonly minWordPressEditor?: WordPressVersion;
|
|
43
|
+
readonly minWordPressFieldsList?: WordPressVersion;
|
|
44
|
+
readonly minWordPressServer?: WordPressVersion;
|
|
45
|
+
readonly minWordPressSupportedAttributesFilter?: WordPressVersion;
|
|
46
|
+
readonly onDiagnostic?: (diagnostic: BindingSourceDiagnostic) => void;
|
|
47
|
+
readonly server?: boolean;
|
|
48
|
+
readonly strict?: boolean;
|
|
49
|
+
readonly supportedAttributesFilter?: boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface DefineBindingSourceOptions extends WordPressCompatibilitySettings {
|
|
52
|
+
readonly editor?: boolean;
|
|
53
|
+
readonly fieldsList?: boolean;
|
|
54
|
+
readonly logger?: DiagnosticLogger<BindingSourceDiagnostic>;
|
|
55
|
+
readonly minWordPress?: WordPressVersion | BindingSourceVersionGates;
|
|
56
|
+
readonly minWordPressEditor?: WordPressVersion;
|
|
57
|
+
readonly minWordPressFieldsList?: WordPressVersion;
|
|
58
|
+
readonly minWordPressServer?: WordPressVersion;
|
|
59
|
+
readonly minWordPressSupportedAttributesFilter?: WordPressVersion;
|
|
60
|
+
readonly onDiagnostic?: (diagnostic: BindingSourceDiagnostic) => void;
|
|
61
|
+
readonly server?: boolean;
|
|
62
|
+
readonly supportedAttributesFilter?: boolean;
|
|
63
|
+
}
|
|
64
|
+
export type StripDefineBindingSourceOptions<TSource> = Omit<TSource, keyof DefineBindingSourceInlineOptions>;
|
|
65
|
+
export declare const DEFINED_BLOCK_BINDING_SOURCE_METADATA: unique symbol;
|
|
66
|
+
export type DefinedBlockBindingSourceMetadataKey = typeof DEFINED_BLOCK_BINDING_SOURCE_METADATA;
|
|
67
|
+
export interface DefinedBlockBindingSourceMetadata {
|
|
68
|
+
readonly diagnostics: readonly BindingSourceDiagnostic[];
|
|
69
|
+
readonly features: readonly WordPressBlockApiCompatibilityFeature[];
|
|
70
|
+
readonly manifest: WordPressBlockApiCompatibilityManifest;
|
|
71
|
+
}
|
|
72
|
+
export type DefinedBindingSource<TName extends string = string, TArgs extends BindingSourceArgs = BindingSourceArgs, TFields extends readonly BindingSourceField[] = readonly BindingSourceField[], TSource extends BindingSourceDefinition = BindingSourceDefinition> = Readonly<StripDefineBindingSourceOptions<TSource>> & {
|
|
73
|
+
readonly [DEFINED_BLOCK_BINDING_SOURCE_METADATA]?: DefinedBlockBindingSourceMetadata | undefined;
|
|
74
|
+
readonly __wpTypiaBindingSourceArgs?: TArgs;
|
|
75
|
+
readonly __wpTypiaBindingSourceFields?: TFields;
|
|
76
|
+
readonly name: TName;
|
|
77
|
+
};
|
|
78
|
+
export interface BlockBinding<TSourceName extends string = string, TArgs extends BindingSourceArgs = BindingSourceArgs> {
|
|
79
|
+
readonly args?: TArgs;
|
|
80
|
+
readonly source: TSourceName;
|
|
81
|
+
}
|
|
82
|
+
type BindingSourceInferredArgs<TSource> = TSource extends {
|
|
83
|
+
readonly __wpTypiaBindingSourceArgs?: infer TArgs extends BindingSourceArgs;
|
|
84
|
+
} ? TArgs : BindingSourceArgs;
|
|
85
|
+
export type Binding<TSource extends DefinedBindingSource | string, TArgs extends BindingSourceArgs = BindingSourceInferredArgs<TSource>> = TSource extends DefinedBindingSource<infer TName, infer TSourceArgs> ? TArgs extends TSourceArgs ? BlockBinding<TName, TArgs> : never : TSource extends string ? BlockBinding<TSource, TArgs> : never;
|
|
86
|
+
export type BlockBindingMap<TAttributes extends BlockAttributes = BlockAttributes> = Readonly<Partial<Record<BlockBindingAttributeName<TAttributes>, BlockBinding>>>;
|
|
87
|
+
export interface BlockMetadataBindings<TBindings extends Readonly<Record<string, BlockBinding | undefined>> = Readonly<Record<string, BlockBinding>>> {
|
|
88
|
+
readonly bindings?: TBindings;
|
|
89
|
+
}
|
|
90
|
+
export type TypedBlockMetadataBindings<TAttributes extends BlockAttributes, TBindings extends BlockBindingMap<TAttributes> = BlockBindingMap<TAttributes>> = BlockMetadataBindings<TBindings>;
|
|
91
|
+
export type BindingSourceDiagnosticCode = "duplicate-bindable-attribute" | "duplicate-field-name" | "fields-list-requires-editor" | "invalid-bindable-attribute" | "invalid-block-name" | "invalid-field-name" | "invalid-source-name" | "missing-php-callback";
|
|
92
|
+
export interface BindingSourceAuthoringDiagnostic {
|
|
93
|
+
readonly attribute?: string | undefined;
|
|
94
|
+
readonly blockName?: string | undefined;
|
|
95
|
+
readonly code: BindingSourceDiagnosticCode;
|
|
96
|
+
readonly fieldName?: string | undefined;
|
|
97
|
+
readonly message: string;
|
|
98
|
+
readonly severity: "error" | "warning";
|
|
99
|
+
readonly sourceName: string;
|
|
100
|
+
}
|
|
101
|
+
export type BindingSourceDiagnostic = BindingSourceAuthoringDiagnostic | WordPressBlockApiCompatibilityDiagnostic;
|
|
102
|
+
export interface BindingSourceRegistrationEntry {
|
|
103
|
+
readonly metadata: DefinedBlockBindingSourceMetadata;
|
|
104
|
+
readonly source: DefinedBindingSource;
|
|
105
|
+
}
|
|
106
|
+
export declare function getDefinedBindingSourceMetadata(source: unknown): DefinedBlockBindingSourceMetadata | undefined;
|
|
107
|
+
export declare function getDefinedBindingSourceCompatibilityManifest(source: unknown): WordPressBlockApiCompatibilityManifest | undefined;
|
|
108
|
+
export declare function defineBindingSource<const TSource extends BindingSourceDefinition & DefineBindingSourceInlineOptions>(source: TSource, options?: DefineBindingSourceOptions): DefinedBindingSource<Extract<TSource["name"], string>, TSource extends {
|
|
109
|
+
readonly args: infer TArgs extends BindingSourceArgs;
|
|
110
|
+
} ? TArgs : BindingSourceArgs, TSource extends {
|
|
111
|
+
readonly fields: infer TFields extends readonly BindingSourceField[];
|
|
112
|
+
} ? TFields : readonly BindingSourceField[], TSource>;
|
|
113
|
+
export declare function defineBindableAttributes<TAttributes extends BlockAttributes = BlockAttributes, const TBlockName extends string = string, const TAttributesList extends readonly BlockBindingAttributeName<TAttributes>[] = readonly BlockBindingAttributeName<TAttributes>[]>(blockName: TBlockName, attributes: TAttributesList): BindingSourceBindableAttributes<TAttributes, TBlockName, TAttributesList>;
|
|
114
|
+
export declare function defineBlockMetadataBindings<const TBindings extends Readonly<Record<string, BlockBinding | undefined>>>(bindings: TBindings): BlockMetadataBindings<TBindings>;
|
|
115
|
+
export declare function defineTypedBlockMetadataBindings<TAttributes extends BlockAttributes, const TBindings extends BlockBindingMap<TAttributes> = BlockBindingMap<TAttributes>>(bindings: TBindings): TypedBlockMetadataBindings<TAttributes, TBindings>;
|
|
116
|
+
export declare function createBindingSourceRegistrationPlan(sources: readonly DefinedBindingSource[]): readonly BindingSourceRegistrationEntry[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { isObjectRecord } from "./shared/object-utils.js";
|
|
2
|
+
import { createBindingSourceDiagnostics, handleBindingSourceDiagnostics, } from "./bindings-diagnostics.js";
|
|
3
|
+
import { collectBindingSourceCompatibilityFeatures, createBindingCompatibilityManifest, createBindingSourceCompatibilityManifest, } from "./bindings-manifest.js";
|
|
4
|
+
import { resolveDefineBindingSourceSettings, splitDefineBindingSourceInput, } from "./bindings-settings.js";
|
|
5
|
+
export { collectBindingSourceCompatibilityFeatures, createBindingSourceCompatibilityManifest, };
|
|
6
|
+
export const DEFINED_BLOCK_BINDING_SOURCE_METADATA = Symbol.for("@wp-typia/block-types/defined-binding-source");
|
|
7
|
+
export function getDefinedBindingSourceMetadata(source) {
|
|
8
|
+
return isObjectRecord(source)
|
|
9
|
+
? source[DEFINED_BLOCK_BINDING_SOURCE_METADATA]
|
|
10
|
+
: undefined;
|
|
11
|
+
}
|
|
12
|
+
export function getDefinedBindingSourceCompatibilityManifest(source) {
|
|
13
|
+
return getDefinedBindingSourceMetadata(source)?.manifest;
|
|
14
|
+
}
|
|
15
|
+
export function defineBindingSource(source, options = {}) {
|
|
16
|
+
const { inlineOptions, source: normalizedSource } = splitDefineBindingSourceInput(source);
|
|
17
|
+
const resolved = resolveDefineBindingSourceSettings(inlineOptions, options, normalizedSource);
|
|
18
|
+
const features = collectBindingSourceCompatibilityFeatures(resolved.features);
|
|
19
|
+
const manifest = createBindingCompatibilityManifest(features, resolved.compatibility, resolved.gates);
|
|
20
|
+
const diagnostics = [
|
|
21
|
+
...manifest.diagnostics,
|
|
22
|
+
...createBindingSourceDiagnostics(normalizedSource, {
|
|
23
|
+
editor: resolved.features.editor,
|
|
24
|
+
fieldsList: resolved.features.fieldsList,
|
|
25
|
+
server: resolved.features.server,
|
|
26
|
+
strict: resolved.strict,
|
|
27
|
+
}),
|
|
28
|
+
];
|
|
29
|
+
handleBindingSourceDiagnostics(diagnostics, resolved.onDiagnostic, resolved.logger);
|
|
30
|
+
Object.defineProperty(normalizedSource, DEFINED_BLOCK_BINDING_SOURCE_METADATA, {
|
|
31
|
+
configurable: false,
|
|
32
|
+
enumerable: false,
|
|
33
|
+
value: {
|
|
34
|
+
diagnostics,
|
|
35
|
+
features,
|
|
36
|
+
manifest,
|
|
37
|
+
},
|
|
38
|
+
writable: false,
|
|
39
|
+
});
|
|
40
|
+
return normalizedSource;
|
|
41
|
+
}
|
|
42
|
+
export function defineBindableAttributes(blockName, attributes) {
|
|
43
|
+
return {
|
|
44
|
+
attributes,
|
|
45
|
+
blockName,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function defineBlockMetadataBindings(bindings) {
|
|
49
|
+
return { bindings };
|
|
50
|
+
}
|
|
51
|
+
export function defineTypedBlockMetadataBindings(bindings) {
|
|
52
|
+
return { bindings };
|
|
53
|
+
}
|
|
54
|
+
export function createBindingSourceRegistrationPlan(sources) {
|
|
55
|
+
return sources.map((source) => {
|
|
56
|
+
const metadata = getDefinedBindingSourceMetadata(source);
|
|
57
|
+
if (!metadata) {
|
|
58
|
+
throw new Error(`Block binding source "${source.name}" was not created by defineBindingSource().`);
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
metadata,
|
|
62
|
+
source,
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BindingSourceAuthoringDiagnostic, BindingSourceDefinition, BindingSourceDiagnostic, DefineBindingSourceOptions } from "./bindings-core.js";
|
|
2
|
+
export declare function createBindingSourceDiagnostics(source: BindingSourceDefinition, options: {
|
|
3
|
+
readonly editor: boolean;
|
|
4
|
+
readonly fieldsList: boolean;
|
|
5
|
+
readonly server: boolean;
|
|
6
|
+
readonly strict: boolean;
|
|
7
|
+
}): readonly BindingSourceAuthoringDiagnostic[];
|
|
8
|
+
export declare function handleBindingSourceDiagnostics(diagnostics: readonly BindingSourceDiagnostic[], onDiagnostic: DefineBindingSourceOptions["onDiagnostic"], logger: DefineBindingSourceOptions["logger"]): void;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { getDiagnosticSeverity, handleDiagnostics } from "./shared/diagnostics.js";
|
|
2
|
+
const SOURCE_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/u;
|
|
3
|
+
const FIELD_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_-]*$/u;
|
|
4
|
+
export function createBindingSourceDiagnostics(source, options) {
|
|
5
|
+
const diagnostics = [];
|
|
6
|
+
const severity = getDiagnosticSeverity(options.strict);
|
|
7
|
+
if (!SOURCE_NAME_PATTERN.test(source.name)) {
|
|
8
|
+
diagnostics.push({
|
|
9
|
+
code: "invalid-source-name",
|
|
10
|
+
message: `Block binding source "${source.name}" must be lowercase and namespaced, such as "acme/profile-data".`,
|
|
11
|
+
severity,
|
|
12
|
+
sourceName: source.name,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
if (options.server && !source.getValueCallback) {
|
|
16
|
+
diagnostics.push({
|
|
17
|
+
code: "missing-php-callback",
|
|
18
|
+
message: `Block binding source "${source.name}" needs getValueCallback when server registration is enabled.`,
|
|
19
|
+
severity,
|
|
20
|
+
sourceName: source.name,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
if (options.fieldsList && !options.editor) {
|
|
24
|
+
diagnostics.push({
|
|
25
|
+
code: "fields-list-requires-editor",
|
|
26
|
+
message: `Block binding source "${source.name}" enables getFieldsList() without editor registration.`,
|
|
27
|
+
severity,
|
|
28
|
+
sourceName: source.name,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const seenFields = new Set();
|
|
32
|
+
for (const field of source.fields ?? []) {
|
|
33
|
+
if (!FIELD_NAME_PATTERN.test(field.name)) {
|
|
34
|
+
diagnostics.push({
|
|
35
|
+
code: "invalid-field-name",
|
|
36
|
+
fieldName: field.name,
|
|
37
|
+
message: `Block binding source "${source.name}" field "${field.name}" must be a stable identifier.`,
|
|
38
|
+
severity,
|
|
39
|
+
sourceName: source.name,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (seenFields.has(field.name)) {
|
|
43
|
+
diagnostics.push({
|
|
44
|
+
code: "duplicate-field-name",
|
|
45
|
+
fieldName: field.name,
|
|
46
|
+
message: `Block binding source "${source.name}" declares duplicate field "${field.name}".`,
|
|
47
|
+
severity,
|
|
48
|
+
sourceName: source.name,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
seenFields.add(field.name);
|
|
52
|
+
}
|
|
53
|
+
for (const target of source.bindableAttributes ?? []) {
|
|
54
|
+
if (!SOURCE_NAME_PATTERN.test(target.blockName)) {
|
|
55
|
+
diagnostics.push({
|
|
56
|
+
blockName: target.blockName,
|
|
57
|
+
code: "invalid-block-name",
|
|
58
|
+
message: `Bindable attributes target "${target.blockName}" must be a lowercase namespaced block name.`,
|
|
59
|
+
severity,
|
|
60
|
+
sourceName: source.name,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const seenAttributes = new Set();
|
|
64
|
+
for (const attribute of target.attributes) {
|
|
65
|
+
if (!FIELD_NAME_PATTERN.test(attribute)) {
|
|
66
|
+
diagnostics.push({
|
|
67
|
+
attribute,
|
|
68
|
+
blockName: target.blockName,
|
|
69
|
+
code: "invalid-bindable-attribute",
|
|
70
|
+
message: `Bindable attribute "${attribute}" for "${target.blockName}" must be a stable identifier.`,
|
|
71
|
+
severity,
|
|
72
|
+
sourceName: source.name,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (seenAttributes.has(attribute)) {
|
|
76
|
+
diagnostics.push({
|
|
77
|
+
attribute,
|
|
78
|
+
blockName: target.blockName,
|
|
79
|
+
code: "duplicate-bindable-attribute",
|
|
80
|
+
message: `Bindable attribute "${attribute}" for "${target.blockName}" is declared more than once.`,
|
|
81
|
+
severity,
|
|
82
|
+
sourceName: source.name,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
seenAttributes.add(attribute);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return diagnostics;
|
|
89
|
+
}
|
|
90
|
+
export function handleBindingSourceDiagnostics(diagnostics, onDiagnostic, logger) {
|
|
91
|
+
handleDiagnostics(diagnostics, onDiagnostic, {
|
|
92
|
+
failureHeading: "WordPress block binding source check failed:",
|
|
93
|
+
logger,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type WordPressBlockApiCompatibilityFeature, type WordPressBlockApiCompatibilityManifest, type WordPressCompatibilitySettings } from "./compatibility.js";
|
|
2
|
+
import type { BindingSourceVersionGates, DefineBindingSourceOptions } from "./bindings-core.js";
|
|
3
|
+
export interface BindingSourceCompatibilityFeatureSettings {
|
|
4
|
+
readonly editor?: boolean;
|
|
5
|
+
readonly fieldsList?: boolean;
|
|
6
|
+
readonly metadata?: boolean;
|
|
7
|
+
readonly server?: boolean;
|
|
8
|
+
readonly supportedAttributesFilter?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function createBindingCompatibilityManifest(features: readonly WordPressBlockApiCompatibilityFeature[], settings: WordPressCompatibilitySettings, gates: BindingSourceVersionGates): WordPressBlockApiCompatibilityManifest;
|
|
11
|
+
export declare function collectBindingSourceCompatibilityFeatures(settings?: BindingSourceCompatibilityFeatureSettings): readonly WordPressBlockApiCompatibilityFeature[];
|
|
12
|
+
export declare function createBindingSourceCompatibilityManifest(settings?: DefineBindingSourceOptions): WordPressBlockApiCompatibilityManifest;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { DEFAULT_WORDPRESS_COMPATIBILITY_MIN_VERSION, evaluateWordPressBlockApiCompatibility, } from "./compatibility.js";
|
|
2
|
+
import { resolveDefineBindingSourceSettings } from "./bindings-settings.js";
|
|
3
|
+
function getFeatureMinVersion(feature, fallback, gates) {
|
|
4
|
+
if (feature.area !== "blockBindings") {
|
|
5
|
+
return fallback;
|
|
6
|
+
}
|
|
7
|
+
switch (feature.feature) {
|
|
8
|
+
case "metadata.bindings":
|
|
9
|
+
case "serverRegistration":
|
|
10
|
+
return gates.server ?? fallback;
|
|
11
|
+
case "editorFieldsList":
|
|
12
|
+
return gates.fieldsList ?? fallback;
|
|
13
|
+
case "editorRegistration":
|
|
14
|
+
case "editorSourceLookup":
|
|
15
|
+
return gates.editor ?? fallback;
|
|
16
|
+
case "supportedAttributesFilter":
|
|
17
|
+
return gates.supportedAttributesFilter ?? gates.fieldsList ?? fallback;
|
|
18
|
+
default:
|
|
19
|
+
return fallback;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function createBindingCompatibilityManifest(features, settings, gates) {
|
|
23
|
+
const fallback = settings.minVersion ?? DEFAULT_WORDPRESS_COMPATIBILITY_MIN_VERSION;
|
|
24
|
+
const strict = settings.strict ?? true;
|
|
25
|
+
const allowUnknownFutureKeys = settings.allowUnknownFutureKeys ?? false;
|
|
26
|
+
const evaluations = features.map((feature) => evaluateWordPressBlockApiCompatibility(feature, {
|
|
27
|
+
allowUnknownFutureKeys,
|
|
28
|
+
minVersion: getFeatureMinVersion(feature, fallback, gates),
|
|
29
|
+
strict,
|
|
30
|
+
}));
|
|
31
|
+
const diagnostics = evaluations.flatMap((evaluation) => evaluation.diagnostic ? [evaluation.diagnostic] : []);
|
|
32
|
+
return {
|
|
33
|
+
allowUnknownFutureKeys,
|
|
34
|
+
diagnostics,
|
|
35
|
+
evaluations,
|
|
36
|
+
minVersion: fallback,
|
|
37
|
+
strict,
|
|
38
|
+
supported: evaluations.filter((evaluation) => evaluation.status === "supported"),
|
|
39
|
+
unknown: evaluations.filter((evaluation) => evaluation.status === "unknown"),
|
|
40
|
+
unsupported: evaluations.filter((evaluation) => evaluation.status === "unsupported"),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function collectBindingSourceCompatibilityFeatures(settings = {}) {
|
|
44
|
+
const features = [];
|
|
45
|
+
if (settings.metadata ?? true) {
|
|
46
|
+
features.push({
|
|
47
|
+
area: "blockBindings",
|
|
48
|
+
feature: "metadata.bindings",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (settings.server ?? true) {
|
|
52
|
+
features.push({
|
|
53
|
+
area: "blockBindings",
|
|
54
|
+
feature: "serverRegistration",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (settings.editor ?? true) {
|
|
58
|
+
features.push({
|
|
59
|
+
area: "blockBindings",
|
|
60
|
+
feature: "editorRegistration",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (settings.fieldsList ?? false) {
|
|
64
|
+
features.push({
|
|
65
|
+
area: "blockBindings",
|
|
66
|
+
feature: "editorFieldsList",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (settings.supportedAttributesFilter ?? false) {
|
|
70
|
+
features.push({
|
|
71
|
+
area: "blockBindings",
|
|
72
|
+
feature: "supportedAttributesFilter",
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return features;
|
|
76
|
+
}
|
|
77
|
+
export function createBindingSourceCompatibilityManifest(settings = {}) {
|
|
78
|
+
const resolved = resolveDefineBindingSourceSettings({}, settings, {
|
|
79
|
+
name: "wp-typia/binding-source",
|
|
80
|
+
});
|
|
81
|
+
return createBindingCompatibilityManifest(collectBindingSourceCompatibilityFeatures(resolved.features), resolved.compatibility, resolved.gates);
|
|
82
|
+
}
|