@wp-typia/block-types 0.2.4 → 0.3.1
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 +309 -15
- package/dist/blocks/bindings-codegen.d.ts +13 -0
- package/dist/blocks/bindings-codegen.js +210 -0
- package/dist/blocks/bindings-core.d.ts +123 -0
- package/dist/blocks/bindings-core.js +351 -0
- package/dist/blocks/bindings.d.ts +2 -0
- package/dist/blocks/bindings.js +2 -0
- package/dist/blocks/compatibility.d.ts +296 -0
- package/dist/blocks/compatibility.js +338 -0
- package/dist/blocks/index.d.ts +3 -0
- package/dist/blocks/index.js +3 -0
- 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.d.ts +57 -2
- package/dist/blocks/supports.js +172 -0
- package/dist/blocks/variations.d.ts +87 -0
- package/dist/blocks/variations.js +258 -0
- package/package.json +16 -1
package/README.md
CHANGED
|
@@ -18,8 +18,11 @@ Shared WordPress block semantic types derived from Gutenberg source and unoffici
|
|
|
18
18
|
- `@wp-typia/block-types/block-editor/style-attributes`
|
|
19
19
|
- `@wp-typia/block-types/block-editor/typography`
|
|
20
20
|
- `@wp-typia/block-types/blocks`
|
|
21
|
+
- `@wp-typia/block-types/blocks/bindings`
|
|
22
|
+
- `@wp-typia/block-types/blocks/compatibility`
|
|
21
23
|
- `@wp-typia/block-types/blocks/registration`
|
|
22
24
|
- `@wp-typia/block-types/blocks/supports`
|
|
25
|
+
- `@wp-typia/block-types/blocks/variations`
|
|
23
26
|
|
|
24
27
|
## Current policy
|
|
25
28
|
|
|
@@ -43,6 +46,12 @@ Shared WordPress block semantic types derived from Gutenberg source and unoffici
|
|
|
43
46
|
migration-facing `BlockInstance`
|
|
44
47
|
- additive stable Core coverage for drop caps, spacing sizes, layout gaps,
|
|
45
48
|
duotone, per-side border widths, and `js` / `locking`
|
|
49
|
+
- a WordPress block API compatibility matrix for Supports, Variations, and
|
|
50
|
+
Bindings features that codegen and diagnostics can share
|
|
51
|
+
- typed Block Variations authoring helpers with static JavaScript registration
|
|
52
|
+
source generation
|
|
53
|
+
- typed Block Bindings source helpers with PHP/editor source generation and
|
|
54
|
+
`metadata.bindings` type helpers
|
|
46
55
|
|
|
47
56
|
## Registration facade
|
|
48
57
|
|
|
@@ -71,6 +80,80 @@ TypeScript installs surface the requirement explicitly.
|
|
|
71
80
|
Compatibility should track that floor unless the generated project dependency
|
|
72
81
|
matrix changes in the same release.
|
|
73
82
|
|
|
83
|
+
## WordPress block API compatibility
|
|
84
|
+
|
|
85
|
+
`@wp-typia/block-types/blocks/compatibility` exposes the shared compatibility
|
|
86
|
+
foundation used by future block Supports, Variations, and Bindings helpers.
|
|
87
|
+
The matrix records documented WordPress version floors, runtime surfaces, derived
|
|
88
|
+
attributes, fallback hints, and source URLs for feature checks.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { createWordPressBlockApiCompatibilityManifest } from '@wp-typia/block-types/blocks/compatibility';
|
|
92
|
+
|
|
93
|
+
const manifest = createWordPressBlockApiCompatibilityManifest(
|
|
94
|
+
[
|
|
95
|
+
{ area: 'blockSupports', feature: 'allowedBlocks' },
|
|
96
|
+
{ area: 'blockBindings', feature: 'editorRegistration' },
|
|
97
|
+
],
|
|
98
|
+
{
|
|
99
|
+
minVersion: '6.7',
|
|
100
|
+
strict: true,
|
|
101
|
+
allowUnknownFutureKeys: false,
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Strict mode marks known unsupported features as errors and recommends skipping
|
|
107
|
+
generation. Non-strict mode downgrades them to warnings and recommends guarded
|
|
108
|
+
generation. Unknown future keys are guarded by default, or passed through only
|
|
109
|
+
when `allowUnknownFutureKeys` is enabled.
|
|
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
|
+
|
|
74
157
|
## Validation coverage
|
|
75
158
|
|
|
76
159
|
`@wp-typia/block-types` now validates itself with a mixed strategy that matches
|
|
@@ -88,35 +171,73 @@ breakage in scaffold or runtime packages.
|
|
|
88
171
|
|
|
89
172
|
## WordPress style support helpers
|
|
90
173
|
|
|
91
|
-
The package now exposes
|
|
174
|
+
The package now exposes three complementary surfaces:
|
|
92
175
|
|
|
93
|
-
- `@wp-typia/block-types/blocks/supports` for typed `block.json` `supports
|
|
176
|
+
- `@wp-typia/block-types/blocks/supports` for typed `block.json` `supports`,
|
|
177
|
+
the `defineSupports()` helper, and `SupportAttributes<typeof supports>`
|
|
94
178
|
- `@wp-typia/block-types/block-editor/style-attributes` for the attribute and `style` shapes WordPress injects when those supports are enabled
|
|
179
|
+
- `@wp-typia/block-types/blocks/compatibility` for version-aware diagnostics
|
|
180
|
+
when a support key requires a newer WordPress floor
|
|
95
181
|
|
|
96
182
|
Example:
|
|
97
183
|
|
|
98
184
|
```ts
|
|
99
|
-
import
|
|
100
|
-
|
|
185
|
+
import {
|
|
186
|
+
defineSupports,
|
|
187
|
+
type SupportAttributes,
|
|
188
|
+
} from '@wp-typia/block-types/blocks/supports';
|
|
101
189
|
|
|
102
|
-
const supports
|
|
103
|
-
|
|
190
|
+
const supports = defineSupports({
|
|
191
|
+
minWordPress: '6.6',
|
|
192
|
+
color: { text: true, background: true },
|
|
104
193
|
spacing: {
|
|
105
194
|
padding: true,
|
|
106
|
-
|
|
107
|
-
|
|
195
|
+
margin: true,
|
|
196
|
+
blockGap: true,
|
|
197
|
+
},
|
|
198
|
+
typography: {
|
|
199
|
+
fontSize: true,
|
|
200
|
+
lineHeight: true,
|
|
201
|
+
letterSpacing: true,
|
|
202
|
+
textAlign: ['left', 'center'],
|
|
203
|
+
},
|
|
204
|
+
layout: {
|
|
205
|
+
default: {
|
|
206
|
+
type: 'constrained',
|
|
207
|
+
},
|
|
108
208
|
},
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
209
|
+
anchor: true,
|
|
210
|
+
html: false,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
type OwnAttributes = {
|
|
214
|
+
content: string;
|
|
215
|
+
density: 'compact' | 'balanced' | 'airy';
|
|
112
216
|
};
|
|
113
217
|
|
|
114
|
-
type
|
|
115
|
-
BlockStyleSupportAttributes,
|
|
116
|
-
'backgroundColor' | 'fontSize' | 'style' | 'textColor'
|
|
117
|
-
>;
|
|
218
|
+
type BlockAttributes = OwnAttributes & SupportAttributes<typeof supports>;
|
|
118
219
|
```
|
|
119
220
|
|
|
221
|
+
`defineSupports()` returns a plain object that can be written directly to
|
|
222
|
+
`block.json.supports`; inline helper controls such as `minWordPress`, `strict`,
|
|
223
|
+
and `allowUnknownFutureKeys` are stripped from the returned metadata. The helper
|
|
224
|
+
stores its compatibility manifest on a non-enumerable symbol so generated JSON
|
|
225
|
+
stays clean while tests and codegen can still inspect diagnostics through
|
|
226
|
+
`getDefinedSupportsCompatibilityManifest()`.
|
|
227
|
+
|
|
228
|
+
Strict mode is enabled by default. Known support keys that require a newer
|
|
229
|
+
WordPress version throw, while `strict: false` reports warnings through
|
|
230
|
+
`onDiagnostic` and keeps the metadata pass-through. Unknown future top-level
|
|
231
|
+
support keys are rejected unless `allowUnknownFutureKeys` is enabled.
|
|
232
|
+
|
|
233
|
+
`SupportAttributes<typeof supports>` is intentionally conservative where
|
|
234
|
+
Gutenberg behavior is broad. Enabling color, spacing, typography, border,
|
|
235
|
+
dimensions, background, filter duotone, position, or shadow includes the shared
|
|
236
|
+
`style` attribute shape. Typography also exposes slug attributes such as
|
|
237
|
+
`fontSize` and `fontFamily` when typography support is enabled; the compatibility
|
|
238
|
+
matrix tracks version-gated typography keys like `textAlign` separately from
|
|
239
|
+
longstanding keys such as `dropCap`.
|
|
240
|
+
|
|
120
241
|
Stable Core coverage now also includes support/style helpers for layout
|
|
121
242
|
`rowGap` / `columnGap`, color `duotone`, per-side border widths, and other
|
|
122
243
|
recently stabilized support keys.
|
|
@@ -126,6 +247,179 @@ for blocks that compute style output on the server. This follows Gutenberg's
|
|
|
126
247
|
current experimental surface and should not be treated as a long-term
|
|
127
248
|
stability guarantee.
|
|
128
249
|
|
|
250
|
+
## WordPress block variation helpers
|
|
251
|
+
|
|
252
|
+
`@wp-typia/block-types/blocks/variations` provides a type-first layer over the
|
|
253
|
+
native Block Variations API. `defineVariation()` returns a plain variation
|
|
254
|
+
metadata object suitable for `registerBlockVariation(...)`; the target block name
|
|
255
|
+
and compatibility manifest are stored on a non-enumerable symbol so generated
|
|
256
|
+
registration code can still recover the block target.
|
|
257
|
+
|
|
258
|
+
Static registration source generation serializes JSON-compatible variation
|
|
259
|
+
metadata. Function-based `isActive` callbacks are accepted by the type helper,
|
|
260
|
+
but `createStaticBlockVariationRegistrationSource()` rejects them; use a dynamic
|
|
261
|
+
registration path aligned with the `blockVariations.phpVariationCallback`
|
|
262
|
+
compatibility entry when callbacks must stay executable.
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
import {
|
|
266
|
+
createStaticBlockVariationRegistrationSource,
|
|
267
|
+
defineVariation,
|
|
268
|
+
defineVariations,
|
|
269
|
+
} from '@wp-typia/block-types/blocks/variations';
|
|
270
|
+
|
|
271
|
+
type HeadingVariationAttributes = {
|
|
272
|
+
className?: string;
|
|
273
|
+
level?: number;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const paragraphVariation = defineVariation('core/paragraph', {
|
|
277
|
+
name: 'example-balanced-paragraph',
|
|
278
|
+
title: 'Balanced Paragraph',
|
|
279
|
+
attributes: {
|
|
280
|
+
className: 'is-style-example-balanced',
|
|
281
|
+
},
|
|
282
|
+
scope: ['inserter', 'transform'],
|
|
283
|
+
isActive: ['className'],
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const headingVariation = defineVariation<HeadingVariationAttributes>(
|
|
287
|
+
'core/heading',
|
|
288
|
+
{
|
|
289
|
+
name: 'example-balanced-heading',
|
|
290
|
+
title: 'Balanced Heading',
|
|
291
|
+
attributes: {
|
|
292
|
+
className: 'is-style-example-heading',
|
|
293
|
+
level: 2,
|
|
294
|
+
},
|
|
295
|
+
scope: ['inserter', 'transform'],
|
|
296
|
+
isActive: ['className', 'level'],
|
|
297
|
+
},
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const variations = defineVariations([
|
|
301
|
+
paragraphVariation,
|
|
302
|
+
headingVariation,
|
|
303
|
+
] as const);
|
|
304
|
+
|
|
305
|
+
const registrationSource =
|
|
306
|
+
createStaticBlockVariationRegistrationSource(variations);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Custom block variations use the same helper. Pass the custom block name and a
|
|
310
|
+
local attribute type when the variation should be checked against project-owned
|
|
311
|
+
metadata:
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
type TestimonialAttributes = {
|
|
315
|
+
className?: string;
|
|
316
|
+
layout?: 'quote' | 'card';
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
export const featuredTestimonialVariation =
|
|
320
|
+
defineVariation<TestimonialAttributes>('acme/testimonial', {
|
|
321
|
+
name: 'acme-featured-testimonial',
|
|
322
|
+
title: 'Featured Testimonial',
|
|
323
|
+
attributes: {
|
|
324
|
+
className: 'is-style-acme-featured',
|
|
325
|
+
layout: 'card',
|
|
326
|
+
},
|
|
327
|
+
isActive: ['className', 'layout'],
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
`defineVariation()` warns when active-state detection is missing or points at an
|
|
332
|
+
attribute not present in the variation metadata. Use `allowMissingIsActive: true`
|
|
333
|
+
for intentionally passive/default-style variations. `defineVariations()` detects
|
|
334
|
+
duplicate variation names and shared active discriminators for the same target
|
|
335
|
+
block.
|
|
336
|
+
|
|
337
|
+
Static code generation currently targets editor-side
|
|
338
|
+
`registerBlockVariation(...)` calls. Function-based `isActive` callbacks are
|
|
339
|
+
accepted by the type helper, but static source generation rejects them because
|
|
340
|
+
they cannot be represented safely as JSON-backed registration metadata. PHP or
|
|
341
|
+
dynamic variation registration remains scoped to a future helper built on the
|
|
342
|
+
existing `blockVariations.phpVariationCallback` compatibility entry.
|
|
343
|
+
|
|
344
|
+
## WordPress block binding helpers
|
|
345
|
+
|
|
346
|
+
`@wp-typia/block-types/blocks/bindings` provides a type-first layer for the
|
|
347
|
+
Block Bindings API. `defineBindingSource()` returns a plain source metadata
|
|
348
|
+
object, while compatibility diagnostics and codegen metadata are stored on a
|
|
349
|
+
non-enumerable symbol.
|
|
350
|
+
|
|
351
|
+
The helper tracks the documented WordPress floors for server registration
|
|
352
|
+
(`6.5`), editor registration (`6.7`), and editor field lists/custom bindable
|
|
353
|
+
attribute filters (`6.9`). Strict mode throws for unsupported combinations;
|
|
354
|
+
`strict: false` reports diagnostics and omits unsupported editor-only output.
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
import {
|
|
358
|
+
createEditorBindingSourceRegistrationSource,
|
|
359
|
+
createPhpBindingSourceRegistrationSource,
|
|
360
|
+
defineBindableAttributes,
|
|
361
|
+
defineBindingSource,
|
|
362
|
+
defineBlockMetadataBindings,
|
|
363
|
+
type Binding,
|
|
364
|
+
} from '@wp-typia/block-types/blocks/bindings';
|
|
365
|
+
|
|
366
|
+
type ProfileCardAttributes = {
|
|
367
|
+
imageUrl?: string;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const profileSource = defineBindingSource({
|
|
371
|
+
name: 'example/profile-data',
|
|
372
|
+
label: 'Profile Data',
|
|
373
|
+
getValueCallback: 'example_get_profile_binding_value',
|
|
374
|
+
usesContext: ['postId'],
|
|
375
|
+
minWordPress: {
|
|
376
|
+
server: '6.5',
|
|
377
|
+
editor: '6.7',
|
|
378
|
+
fieldsList: '6.9',
|
|
379
|
+
supportedAttributesFilter: '6.9',
|
|
380
|
+
},
|
|
381
|
+
args: {
|
|
382
|
+
field: 'image_url' as 'display_name' | 'image_url',
|
|
383
|
+
},
|
|
384
|
+
fields: [
|
|
385
|
+
{
|
|
386
|
+
name: 'display_name',
|
|
387
|
+
label: 'Display name',
|
|
388
|
+
args: { field: 'display_name' },
|
|
389
|
+
type: 'string',
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
name: 'image_url',
|
|
393
|
+
label: 'Image URL',
|
|
394
|
+
args: { field: 'image_url' },
|
|
395
|
+
type: 'string',
|
|
396
|
+
},
|
|
397
|
+
],
|
|
398
|
+
bindableAttributes: [
|
|
399
|
+
defineBindableAttributes<ProfileCardAttributes>('example/profile-card', [
|
|
400
|
+
'imageUrl',
|
|
401
|
+
] as const),
|
|
402
|
+
],
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const metadata = defineBlockMetadataBindings({
|
|
406
|
+
imageUrl: {
|
|
407
|
+
source: profileSource.name,
|
|
408
|
+
args: { field: 'image_url' },
|
|
409
|
+
} satisfies Binding<typeof profileSource, { field: 'image_url' }>,
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const phpSource = createPhpBindingSourceRegistrationSource(profileSource);
|
|
413
|
+
const editorSource = createEditorBindingSourceRegistrationSource(profileSource);
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
The generated PHP registers `register_block_bindings_source()` on `init` and,
|
|
417
|
+
when custom bindable attributes are declared, adds the
|
|
418
|
+
`block_bindings_supported_attributes_{$block_type}` filter behind a WordPress
|
|
419
|
+
6.9-compatible guard. The generated editor source registers
|
|
420
|
+
`registerBlockBindingsSource()` and emits `getFieldsList()` only when the
|
|
421
|
+
source compatibility manifest marks field lists as supported.
|
|
422
|
+
|
|
129
423
|
## Typia pipeline notes
|
|
130
424
|
|
|
131
425
|
- `CssColorValue` and `MinHeightValue` are richer DX aliases that currently rely on template literal types.
|
|
@@ -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
|
+
}
|