@wp-typia/block-runtime 0.2.3 → 0.3.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/README.md CHANGED
@@ -1,22 +1,19 @@
1
1
  # `@wp-typia/block-runtime`
2
2
 
3
- Prototype block runtime helpers for `wp-typia` generated projects.
3
+ Generated-project runtime and metadata sync helpers for `wp-typia`.
4
4
 
5
- This package is the current graduation path for the stable generated-project
6
- runtime helpers that still live canonically under `@wp-typia/create`.
7
-
8
- It currently re-exports the block runtime helper surface for:
5
+ This is the supported generated-project package boundary for:
9
6
 
10
7
  - manifest-driven defaults
11
8
  - editor model generation
12
9
  - manifest-driven inspector helpers
13
10
  - validation-aware attribute updates
11
+ - TypeScript-to-metadata sync
12
+ - manifest-first REST/OpenAPI/client codegen
14
13
 
15
14
  It does not include:
16
15
 
17
16
  - scaffold or CLI internals
18
- - REST/OpenAPI metadata generation
19
- - schema generation helpers
20
17
  - migration tooling
21
18
 
22
19
  Typical usage:
@@ -25,8 +22,12 @@ Typical usage:
25
22
  import { createEditorModel } from "@wp-typia/block-runtime/editor";
26
23
  import { InspectorFromManifest, useEditorFields } from "@wp-typia/block-runtime/inspector";
27
24
  import { createNestedAttributeUpdater } from "@wp-typia/block-runtime/validation";
25
+ import { runSyncBlockMetadata } from "@wp-typia/block-runtime/metadata-core";
28
26
  ```
29
27
 
30
- `@wp-typia/create` remains the canonical generated-project import surface
31
- through v1. This package exists to validate the long-term package boundary and
32
- migration path without changing scaffolds yet.
28
+ `@wp-typia/create` remains the CLI/scaffolding package.
29
+
30
+ `@wp-typia/create/metadata-core` and `@wp-typia/create/runtime/*` remain
31
+ available as backward-compatible facades, but newly generated projects should
32
+ prefer `@wp-typia/block-runtime/*` and
33
+ `@wp-typia/block-runtime/metadata-core`.
@@ -0,0 +1,180 @@
1
+ import type { BlockSupports as WordPressBlockSupports } from "@wordpress/blocks";
2
+ type EntryMap = Record<string, string>;
3
+ type ScaffoldLayoutType = "flow" | "constrained" | "flex" | "grid";
4
+ type ScaffoldFlexWrap = "wrap" | "nowrap";
5
+ type ScaffoldOrientation = "horizontal" | "vertical";
6
+ type ScaffoldJustifyContent = "left" | "center" | "right" | "space-between" | "stretch";
7
+ type ScaffoldBlockAlignment = "left" | "center" | "right" | "wide" | "full";
8
+ type ScaffoldBlockVerticalAlignment = "top" | "center" | "bottom";
9
+ type ScaffoldSpacingAxis = "horizontal" | "vertical";
10
+ type ScaffoldSpacingDimension = "top" | "right" | "bottom" | "left" | ScaffoldSpacingAxis;
11
+ type ScaffoldTypographySupportKey = "fontFamily" | "fontSize" | "fontStyle" | "fontWeight" | "letterSpacing" | "lineHeight" | "textAlign" | "textColumns" | "textDecoration" | "textTransform" | "writingMode";
12
+ type ScaffoldSpacingSupportKey = "blockGap" | "margin" | "padding";
13
+ type ScaffoldTypographyTextAlignment = "left" | "center" | "right";
14
+ export interface TypiaWebpackArtifactEntry {
15
+ inputPath: string;
16
+ outputPath: string;
17
+ }
18
+ export interface TypiaWebpackConfigOptions {
19
+ defaultConfig: unknown;
20
+ fs: {
21
+ existsSync(path: string): boolean;
22
+ readFileSync(path: string, encoding?: string): string | Buffer;
23
+ writeFileSync(path: string, data: string): void;
24
+ };
25
+ getArtifactEntries: () => TypiaWebpackArtifactEntry[];
26
+ getEditorEntries?: () => EntryMap;
27
+ getOptionalModuleEntries?: () => EntryMap;
28
+ importTypiaWebpackPlugin: () => Promise<{
29
+ default: () => unknown;
30
+ }>;
31
+ isScriptModuleAsset?: (assetName: string) => boolean;
32
+ moduleEntriesMode?: "merge" | "replace";
33
+ nonModuleEntriesMode?: "merge" | "replace";
34
+ path: {
35
+ join(...paths: string[]): string;
36
+ };
37
+ }
38
+ type OverrideProperties<TBase, TOverride> = Omit<TBase, keyof TOverride> & TOverride;
39
+ type ScaffoldSupportDefaultControls<TFeature extends string> = Readonly<Partial<Record<TFeature, boolean>> & Record<string, boolean | undefined>>;
40
+ interface ScaffoldBlockBorderSupport {
41
+ readonly color?: boolean;
42
+ readonly radius?: boolean;
43
+ readonly style?: boolean;
44
+ readonly width?: boolean;
45
+ readonly __experimentalDefaultControls?: ScaffoldSupportDefaultControls<"color" | "radius" | "style" | "width">;
46
+ }
47
+ interface ScaffoldBlockColorSupport {
48
+ readonly background?: boolean;
49
+ readonly button?: boolean;
50
+ readonly enableContrastChecker?: boolean;
51
+ readonly gradients?: boolean;
52
+ readonly heading?: boolean;
53
+ readonly link?: boolean;
54
+ readonly text?: boolean;
55
+ readonly __experimentalDefaultControls?: ScaffoldSupportDefaultControls<"background" | "gradients" | "link" | "text">;
56
+ }
57
+ interface ScaffoldBlockDimensionsSupport {
58
+ readonly aspectRatio?: boolean;
59
+ readonly height?: boolean;
60
+ readonly minHeight?: boolean;
61
+ readonly width?: boolean;
62
+ readonly __experimentalDefaultControls?: ScaffoldSupportDefaultControls<"aspectRatio" | "height" | "minHeight" | "width">;
63
+ }
64
+ interface ScaffoldBlockFilterSupport {
65
+ readonly duotone?: boolean;
66
+ }
67
+ interface ScaffoldBlockInteractivitySupport {
68
+ readonly clientNavigation?: boolean;
69
+ readonly interactive?: boolean;
70
+ }
71
+ interface ScaffoldBlockLayoutDefault {
72
+ readonly columnCount?: number;
73
+ readonly contentSize?: string;
74
+ readonly allowInheriting?: boolean;
75
+ readonly allowSizingOnChildren?: boolean;
76
+ readonly flexWrap?: ScaffoldFlexWrap;
77
+ readonly justifyContent?: ScaffoldJustifyContent;
78
+ readonly minimumColumnWidth?: string;
79
+ readonly orientation?: ScaffoldOrientation;
80
+ readonly type?: ScaffoldLayoutType;
81
+ readonly verticalAlignment?: ScaffoldBlockVerticalAlignment;
82
+ readonly wideSize?: string;
83
+ }
84
+ interface ScaffoldBlockLayoutSupport {
85
+ readonly allowCustomContentAndWideSize?: boolean;
86
+ readonly allowEditing?: boolean;
87
+ readonly allowInheriting?: boolean;
88
+ readonly allowJustification?: boolean;
89
+ readonly allowOrientation?: boolean;
90
+ readonly allowSizingOnChildren?: boolean;
91
+ readonly allowSwitching?: boolean;
92
+ readonly allowVerticalAlignment?: boolean;
93
+ readonly allowWrap?: boolean;
94
+ readonly default?: ScaffoldBlockLayoutDefault;
95
+ }
96
+ interface ScaffoldBlockLightboxSupport {
97
+ readonly allowEditing?: boolean;
98
+ readonly enabled?: boolean;
99
+ }
100
+ interface ScaffoldBlockPositionSupport {
101
+ readonly fixed?: boolean;
102
+ readonly sticky?: boolean;
103
+ readonly __experimentalDefaultControls?: ScaffoldSupportDefaultControls<"fixed" | "sticky">;
104
+ }
105
+ interface ScaffoldBlockShadowSupport {
106
+ readonly __experimentalDefaultControls?: ScaffoldSupportDefaultControls<"shadow">;
107
+ }
108
+ interface ScaffoldBlockSpacingSupport {
109
+ readonly blockGap?: boolean | readonly ScaffoldSpacingAxis[];
110
+ readonly margin?: boolean | readonly ScaffoldSpacingDimension[];
111
+ readonly padding?: boolean | readonly ScaffoldSpacingDimension[];
112
+ readonly __experimentalDefaultControls?: ScaffoldSupportDefaultControls<ScaffoldSpacingSupportKey>;
113
+ }
114
+ interface ScaffoldBlockTypographySupport {
115
+ readonly fontFamily?: boolean;
116
+ readonly fontSize?: boolean;
117
+ readonly fontStyle?: boolean;
118
+ readonly fontWeight?: boolean;
119
+ readonly letterSpacing?: boolean;
120
+ readonly lineHeight?: boolean;
121
+ readonly textAlign?: boolean | readonly ScaffoldTypographyTextAlignment[];
122
+ readonly textColumns?: boolean;
123
+ readonly textDecoration?: boolean;
124
+ readonly textTransform?: boolean;
125
+ readonly writingMode?: boolean;
126
+ readonly __experimentalDefaultControls?: ScaffoldSupportDefaultControls<ScaffoldTypographySupportKey>;
127
+ }
128
+ type ScaffoldBlockSupportsOverride = {
129
+ readonly align?: boolean | readonly ScaffoldBlockAlignment[];
130
+ readonly alignWide?: boolean;
131
+ readonly anchor?: boolean;
132
+ readonly ariaLabel?: boolean;
133
+ readonly border?: boolean | ScaffoldBlockBorderSupport;
134
+ readonly className?: boolean;
135
+ readonly color?: boolean | ScaffoldBlockColorSupport;
136
+ readonly customClassName?: boolean;
137
+ readonly dimensions?: boolean | ScaffoldBlockDimensionsSupport;
138
+ readonly filter?: boolean | ScaffoldBlockFilterSupport;
139
+ readonly html?: boolean;
140
+ readonly inserter?: boolean;
141
+ readonly interactivity?: boolean | ScaffoldBlockInteractivitySupport;
142
+ readonly layout?: boolean | ScaffoldBlockLayoutSupport;
143
+ readonly lightbox?: boolean | ScaffoldBlockLightboxSupport;
144
+ readonly lock?: boolean;
145
+ readonly multiple?: boolean;
146
+ readonly position?: boolean | ScaffoldBlockPositionSupport;
147
+ readonly renaming?: boolean;
148
+ readonly reusable?: boolean;
149
+ readonly shadow?: boolean | ScaffoldBlockShadowSupport;
150
+ readonly spacing?: boolean | ScaffoldBlockSpacingSupport;
151
+ readonly typography?: boolean | ScaffoldBlockTypographySupport;
152
+ };
153
+ export type ScaffoldBlockSupports = OverrideProperties<WordPressBlockSupports, ScaffoldBlockSupportsOverride>;
154
+ export interface ScaffoldBlockRegistrationSettings {
155
+ ancestor?: readonly string[];
156
+ attributes?: Record<string, unknown>;
157
+ category?: unknown;
158
+ description?: unknown;
159
+ example?: unknown;
160
+ icon?: unknown;
161
+ parent?: readonly string[];
162
+ supports?: ScaffoldBlockSupports;
163
+ title?: unknown;
164
+ }
165
+ export interface ScaffoldBlockMetadata extends ScaffoldBlockRegistrationSettings {
166
+ name: string;
167
+ }
168
+ export interface BuildScaffoldBlockRegistrationResult<TSettings extends object> {
169
+ name: string;
170
+ settings: TSettings;
171
+ }
172
+ export declare function buildScaffoldBlockRegistration<TSettings extends object>(metadata: ScaffoldBlockMetadata, overrides: Partial<TSettings> & Record<string, unknown>): BuildScaffoldBlockRegistrationResult<TSettings>;
173
+ export declare function createTypiaWebpackConfig({ defaultConfig, fs, getArtifactEntries, getEditorEntries, getOptionalModuleEntries, importTypiaWebpackPlugin, isScriptModuleAsset, moduleEntriesMode, nonModuleEntriesMode, path, }: TypiaWebpackConfigOptions): Promise<{
174
+ entry: () => Promise<Record<string, unknown>>;
175
+ plugins: unknown[];
176
+ } | {
177
+ entry: () => Promise<Record<string, unknown>>;
178
+ plugins: unknown[];
179
+ }[]>;
180
+ export {};
package/dist/blocks.js ADDED
@@ -0,0 +1,108 @@
1
+ function normalizeScriptModuleAssetSource(source) {
2
+ return String(source).replace(/'dependencies'\s*=>\s*array\([^)]*\)/, "'dependencies' => array()");
3
+ }
4
+ function normalizeEntryMap(entry) {
5
+ if (entry && typeof entry === "object" && !Array.isArray(entry)) {
6
+ return { ...entry };
7
+ }
8
+ return {};
9
+ }
10
+ function toWebpackConfigs(config) {
11
+ return Array.isArray(config) ? config : [config];
12
+ }
13
+ function isModuleConfig(config) {
14
+ return (typeof config === "object" &&
15
+ config !== null &&
16
+ config.output?.module === true);
17
+ }
18
+ function mergeEntries(existingEntries, nextEntries, mode) {
19
+ if (!nextEntries) {
20
+ return existingEntries;
21
+ }
22
+ return mode === "replace"
23
+ ? { ...nextEntries }
24
+ : {
25
+ ...existingEntries,
26
+ ...nextEntries,
27
+ };
28
+ }
29
+ export function buildScaffoldBlockRegistration(metadata, overrides) {
30
+ const name = metadata.name;
31
+ if (typeof name !== "string" || name.length === 0) {
32
+ throw new Error("Scaffold block metadata must include a string name.");
33
+ }
34
+ const { name: _ignoredName, ...metadataSettings } = metadata;
35
+ return {
36
+ name,
37
+ settings: {
38
+ ...metadataSettings,
39
+ ...overrides,
40
+ },
41
+ };
42
+ }
43
+ export async function createTypiaWebpackConfig({ defaultConfig, fs, getArtifactEntries, getEditorEntries, getOptionalModuleEntries, importTypiaWebpackPlugin, isScriptModuleAsset = (assetName) => /(^|\/)(interactivity|view)\.asset\.php$/.test(assetName), moduleEntriesMode = "merge", nonModuleEntriesMode = "merge", path, }) {
44
+ const { default: UnpluginTypia } = await importTypiaWebpackPlugin();
45
+ const resolvedDefaultConfig = typeof defaultConfig === "function"
46
+ ? await defaultConfig()
47
+ : defaultConfig;
48
+ class TypiaArtifactAssetPlugin {
49
+ apply(compiler) {
50
+ compiler.hooks.thisCompilation.tap("TypiaArtifactAssetPlugin", (compilation) => {
51
+ compilation.hooks.processAssets.tap({
52
+ name: "TypiaArtifactAssetPlugin",
53
+ stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
54
+ }, () => {
55
+ const artifactEntries = getArtifactEntries();
56
+ for (const entry of artifactEntries) {
57
+ if (compilation.getAsset(entry.outputPath)) {
58
+ continue;
59
+ }
60
+ compilation.emitAsset(entry.outputPath, new compiler.webpack.sources.RawSource(fs.readFileSync(entry.inputPath)));
61
+ }
62
+ for (const asset of compilation.getAssets()) {
63
+ if (!isScriptModuleAsset(asset.name)) {
64
+ continue;
65
+ }
66
+ compilation.updateAsset(asset.name, new compiler.webpack.sources.RawSource(normalizeScriptModuleAssetSource(asset.source.source())));
67
+ }
68
+ });
69
+ });
70
+ compiler.hooks.afterEmit.tap("TypiaArtifactAssetPlugin", (compilation) => {
71
+ const outputPath = compilation.outputOptions.path;
72
+ if (!outputPath) {
73
+ return;
74
+ }
75
+ for (const asset of compilation.getAssets()) {
76
+ if (!isScriptModuleAsset(asset.name)) {
77
+ continue;
78
+ }
79
+ const assetPath = path.join(outputPath, asset.name);
80
+ if (!fs.existsSync(assetPath)) {
81
+ continue;
82
+ }
83
+ fs.writeFileSync(assetPath, normalizeScriptModuleAssetSource(fs.readFileSync(assetPath, "utf8")));
84
+ }
85
+ });
86
+ }
87
+ }
88
+ const configs = toWebpackConfigs(resolvedDefaultConfig).map((config) => ({
89
+ ...config,
90
+ entry: async () => {
91
+ const existingEntries = normalizeEntryMap(typeof config.entry === "function"
92
+ ? await config.entry()
93
+ : config.entry);
94
+ const editorEntries = getEditorEntries?.();
95
+ const optionalModuleEntries = getOptionalModuleEntries?.();
96
+ if (isModuleConfig(config)) {
97
+ return mergeEntries(existingEntries, optionalModuleEntries, moduleEntriesMode);
98
+ }
99
+ return mergeEntries(existingEntries, editorEntries, nonModuleEntriesMode);
100
+ },
101
+ plugins: [
102
+ UnpluginTypia(),
103
+ ...(config.plugins ?? []),
104
+ new TypiaArtifactAssetPlugin(),
105
+ ],
106
+ }));
107
+ return configs.length === 1 ? configs[0] : configs;
108
+ }
@@ -1 +1,25 @@
1
- export { applyTemplateDefaultsFromManifest, type ManifestDefaultAttribute, type ManifestDefaultsDocument, } from "@wp-typia/create/runtime/defaults";
1
+ type JsonPrimitive = string | number | boolean | null;
2
+ type JsonValue = JsonPrimitive | JsonValue[] | {
3
+ [key: string]: JsonValue;
4
+ };
5
+ export interface ManifestDefaultAttribute {
6
+ typia: {
7
+ defaultValue: JsonValue | null;
8
+ hasDefault: boolean;
9
+ };
10
+ ts: {
11
+ items: ManifestDefaultAttribute | null;
12
+ kind: "string" | "number" | "boolean" | "array" | "object" | "union";
13
+ properties: Record<string, ManifestDefaultAttribute> | null;
14
+ required: boolean;
15
+ union: {
16
+ branches: Record<string, ManifestDefaultAttribute>;
17
+ discriminator: string;
18
+ } | null;
19
+ };
20
+ }
21
+ export interface ManifestDefaultsDocument {
22
+ attributes: Record<string, ManifestDefaultAttribute>;
23
+ }
24
+ export declare function applyTemplateDefaultsFromManifest<T extends object>(manifest: ManifestDefaultsDocument, value: Partial<T>): Partial<T>;
25
+ export {};
package/dist/defaults.js CHANGED
@@ -1 +1,73 @@
1
- export { applyTemplateDefaultsFromManifest, } from "@wp-typia/create/runtime/defaults";
1
+ import { cloneJsonValue } from "./json-utils.js";
2
+ function isListArray(value) {
3
+ return value.every((_, index) => index in value);
4
+ }
5
+ function deriveDefaultValue(attribute) {
6
+ if (attribute.typia.hasDefault) {
7
+ return cloneJsonValue(attribute.typia.defaultValue);
8
+ }
9
+ if (attribute.ts.kind !== "object" || !attribute.ts.properties) {
10
+ return undefined;
11
+ }
12
+ const objectValue = {};
13
+ for (const [name, child] of Object.entries(attribute.ts.properties)) {
14
+ const childDefault = deriveDefaultValue(child);
15
+ if (childDefault !== undefined) {
16
+ objectValue[name] = childDefault;
17
+ }
18
+ }
19
+ return Object.keys(objectValue).length > 0 ? objectValue : undefined;
20
+ }
21
+ function applyDefaultsForObject(value, schema) {
22
+ const result = { ...value };
23
+ for (const [name, attribute] of Object.entries(schema)) {
24
+ if (!(name in result)) {
25
+ const derivedDefault = deriveDefaultValue(attribute);
26
+ if (derivedDefault !== undefined) {
27
+ result[name] = derivedDefault;
28
+ }
29
+ continue;
30
+ }
31
+ result[name] = applyDefaultsForNode(result[name], attribute);
32
+ }
33
+ return result;
34
+ }
35
+ function applyDefaultsForUnion(value, attribute) {
36
+ const union = attribute.ts.union;
37
+ if (!union) {
38
+ return value;
39
+ }
40
+ const discriminator = union.discriminator;
41
+ const branchKey = value[discriminator];
42
+ if (typeof branchKey !== "string" || !(branchKey in union.branches)) {
43
+ return value;
44
+ }
45
+ return applyDefaultsForNode(value, union.branches[branchKey]);
46
+ }
47
+ function applyDefaultsForNode(value, attribute) {
48
+ if (value === null || value === undefined) {
49
+ return value;
50
+ }
51
+ switch (attribute.ts.kind) {
52
+ case "object":
53
+ if (typeof value === "object" && !Array.isArray(value) && attribute.ts.properties) {
54
+ return applyDefaultsForObject(value, attribute.ts.properties);
55
+ }
56
+ return value;
57
+ case "array":
58
+ if (Array.isArray(value) && isListArray(value) && attribute.ts.items) {
59
+ return value.map((item) => applyDefaultsForNode(item, attribute.ts.items));
60
+ }
61
+ return value;
62
+ case "union":
63
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
64
+ return applyDefaultsForUnion(value, attribute);
65
+ }
66
+ return value;
67
+ default:
68
+ return value;
69
+ }
70
+ }
71
+ export function applyTemplateDefaultsFromManifest(manifest, value) {
72
+ return applyDefaultsForObject(value, manifest.attributes);
73
+ }
package/dist/editor.d.ts CHANGED
@@ -1 +1,33 @@
1
- export { createEditorModel, describeEditorField, formatEditorFieldLabel, type EditorControlKind, type EditorFieldDescriptor, type EditorFieldOption, type EditorModelOptions, type JsonValue, type ManifestAttribute, type ManifestConstraints, type ManifestDocument, type ManifestTsKind, } from "@wp-typia/create/runtime/editor";
1
+ import type { JsonValue, ManifestAttribute, ManifestConstraints, ManifestDocument, ManifestTsKind } from "./migration-types.js";
2
+ export type { JsonValue, ManifestAttribute, ManifestConstraints, ManifestDocument, ManifestTsKind, } from "./migration-types.js";
3
+ export type EditorControlKind = "toggle" | "select" | "range" | "number" | "text" | "textarea" | "unsupported";
4
+ export interface EditorFieldOption {
5
+ label: string;
6
+ value: string | number | boolean;
7
+ }
8
+ export interface EditorFieldDescriptor {
9
+ constraints: ManifestConstraints;
10
+ control: EditorControlKind;
11
+ defaultValue: JsonValue | null;
12
+ hasDefault: boolean;
13
+ key: string;
14
+ kind: ManifestTsKind;
15
+ label: string;
16
+ maximum: number | null;
17
+ minimum: number | null;
18
+ options: EditorFieldOption[];
19
+ path: string;
20
+ reason?: string;
21
+ required: boolean;
22
+ step: number | null;
23
+ supported: boolean;
24
+ }
25
+ export interface EditorModelOptions {
26
+ hidden?: string[];
27
+ labels?: Record<string, string>;
28
+ manual?: string[];
29
+ preferTextarea?: string[];
30
+ }
31
+ export declare function formatEditorFieldLabel(path: string): string;
32
+ export declare function describeEditorField(path: string, attribute: ManifestAttribute, options?: EditorModelOptions): EditorFieldDescriptor;
33
+ export declare function createEditorModel(manifest: ManifestDocument, options?: EditorModelOptions): EditorFieldDescriptor[];
package/dist/editor.js CHANGED
@@ -1 +1,165 @@
1
- export { createEditorModel, describeEditorField, formatEditorFieldLabel, } from "@wp-typia/create/runtime/editor";
1
+ const FORMATTED_STRING_MANUAL_FORMATS = new Set([
2
+ "date-time",
3
+ "email",
4
+ "ipv4",
5
+ "ipv6",
6
+ "uri",
7
+ "url",
8
+ "uuid",
9
+ ]);
10
+ const INTEGER_TYPE_TAGS = new Set(["int32", "uint32", "uint64"]);
11
+ const UPPERCASE_TOKENS = new Map([
12
+ ["api", "API"],
13
+ ["css", "CSS"],
14
+ ["id", "ID"],
15
+ ["uri", "URI"],
16
+ ["url", "URL"],
17
+ ["uuid", "UUID"],
18
+ ]);
19
+ function isScalarOptionValue(value) {
20
+ return (typeof value === "string" ||
21
+ typeof value === "number" ||
22
+ typeof value === "boolean");
23
+ }
24
+ function isNumber(value) {
25
+ return typeof value === "number" && Number.isFinite(value);
26
+ }
27
+ function isPathFlagged(path, candidates) {
28
+ return candidates.some((candidate) => path === candidate || path.startsWith(`${candidate}.`));
29
+ }
30
+ function getLabel(path, labels) {
31
+ return labels?.[path] ?? formatEditorFieldLabel(path);
32
+ }
33
+ function getOptions(attribute) {
34
+ if (!Array.isArray(attribute.wp.enum)) {
35
+ return [];
36
+ }
37
+ return attribute.wp.enum.filter(isScalarOptionValue).map((value) => ({
38
+ label: formatEditorFieldLabel(String(value)),
39
+ value,
40
+ }));
41
+ }
42
+ function getStep(constraints) {
43
+ if (isNumber(constraints.multipleOf)) {
44
+ return constraints.multipleOf;
45
+ }
46
+ if (typeof constraints.typeTag === "string" &&
47
+ INTEGER_TYPE_TAGS.has(constraints.typeTag)) {
48
+ return 1;
49
+ }
50
+ return null;
51
+ }
52
+ function getControlKind(path, attribute, options) {
53
+ if (isPathFlagged(path, options.manual ?? [])) {
54
+ return {
55
+ control: "unsupported",
56
+ reason: "This field is intentionally handled manually in the editor.",
57
+ };
58
+ }
59
+ switch (attribute.ts.kind) {
60
+ case "boolean":
61
+ return { control: "toggle" };
62
+ case "number":
63
+ if (isNumber(attribute.typia.constraints.minimum) &&
64
+ isNumber(attribute.typia.constraints.maximum)) {
65
+ return { control: "range" };
66
+ }
67
+ return { control: "number" };
68
+ case "string":
69
+ if (typeof attribute.typia.constraints.format === "string" &&
70
+ FORMATTED_STRING_MANUAL_FORMATS.has(attribute.typia.constraints.format)) {
71
+ return {
72
+ control: "unsupported",
73
+ reason: `Formatted ${attribute.typia.constraints.format} strings should keep manual editor wiring.`,
74
+ };
75
+ }
76
+ if (getOptions(attribute).length > 0) {
77
+ return { control: "select" };
78
+ }
79
+ if (isPathFlagged(path, options.preferTextarea ?? [])) {
80
+ return { control: "textarea" };
81
+ }
82
+ return { control: "text" };
83
+ case "array":
84
+ return {
85
+ control: "unsupported",
86
+ reason: "Array fields are not auto-rendered by the editor helper.",
87
+ };
88
+ case "object":
89
+ return {
90
+ control: "unsupported",
91
+ reason: "Object fields are flattened into individual leaf controls.",
92
+ };
93
+ case "union":
94
+ return {
95
+ control: "unsupported",
96
+ reason: "Union fields must keep manual editor wiring.",
97
+ };
98
+ default:
99
+ return {
100
+ control: "unsupported",
101
+ reason: "This field kind is not supported by the editor helper.",
102
+ };
103
+ }
104
+ }
105
+ export function formatEditorFieldLabel(path) {
106
+ return path
107
+ .split(".")
108
+ .flatMap((segment) => segment.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[\s_-]+/))
109
+ .filter(Boolean)
110
+ .map((part) => {
111
+ const normalized = part.toLowerCase();
112
+ const upper = UPPERCASE_TOKENS.get(normalized);
113
+ return upper ?? normalized.charAt(0).toUpperCase() + normalized.slice(1);
114
+ })
115
+ .join(" ");
116
+ }
117
+ export function describeEditorField(path, attribute, options = {}) {
118
+ const { control, reason } = getControlKind(path, attribute, options);
119
+ const pathSegments = path.split(".");
120
+ const key = pathSegments[pathSegments.length - 1] ?? path;
121
+ return {
122
+ constraints: attribute.typia.constraints,
123
+ control,
124
+ defaultValue: attribute.typia.defaultValue ?? null,
125
+ hasDefault: attribute.typia.hasDefault === true,
126
+ key,
127
+ kind: attribute.ts.kind,
128
+ label: getLabel(path, options.labels),
129
+ maximum: isNumber(attribute.typia.constraints.maximum)
130
+ ? attribute.typia.constraints.maximum
131
+ : null,
132
+ minimum: isNumber(attribute.typia.constraints.minimum)
133
+ ? attribute.typia.constraints.minimum
134
+ : null,
135
+ // Preserve the legacy `@wp-typia/create/runtime/editor` descriptor contract
136
+ // for compatibility shims: enum options are only exposed for select controls.
137
+ options: control === "select" ? getOptions(attribute) : [],
138
+ path,
139
+ reason,
140
+ required: attribute.ts.required === true,
141
+ step: getStep(attribute.typia.constraints),
142
+ supported: control !== "unsupported",
143
+ };
144
+ }
145
+ function collectEditorFields(path, attribute, options) {
146
+ if (isPathFlagged(path, options.hidden ?? [])) {
147
+ return [];
148
+ }
149
+ if (isPathFlagged(path, options.manual ?? [])) {
150
+ return [describeEditorField(path, attribute, options)];
151
+ }
152
+ if (attribute.ts.kind === "object" && attribute.ts.properties) {
153
+ const childFields = Object.entries(attribute.ts.properties).flatMap(([key, child]) => collectEditorFields(`${path}.${key}`, child, options));
154
+ return childFields.length > 0
155
+ ? childFields
156
+ : [describeEditorField(path, attribute, options)];
157
+ }
158
+ return [describeEditorField(path, attribute, options)];
159
+ }
160
+ export function createEditorModel(manifest, options = {}) {
161
+ if (!manifest.attributes) {
162
+ return [];
163
+ }
164
+ return Object.entries(manifest.attributes).flatMap(([path, attribute]) => collectEditorFields(path, attribute, options));
165
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./blocks.js";
1
2
  export * from "./defaults.js";
2
3
  export * from "./editor.js";
3
4
  export * from "./validation.js";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./blocks.js";
1
2
  export * from "./defaults.js";
2
3
  export * from "./editor.js";
3
4
  export * from "./validation.js";