@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.
@@ -0,0 +1,129 @@
1
+ import React, { type ReactNode } from "react";
2
+ import { type EditorFieldDescriptor, type EditorModelOptions, type ManifestDocument } from "./editor.js";
3
+ import { type ValidationResult } from "./validation.js";
4
+ export type { EditorFieldDescriptor, EditorFieldOption, EditorModelOptions, ManifestAttribute, ManifestDocument, } from "./editor.js";
5
+ export type { ValidationResult } from "./validation.js";
6
+ type UnknownRecord = Record<string, unknown>;
7
+ export interface InspectorSelectOption {
8
+ label: string;
9
+ value: string;
10
+ }
11
+ export interface PanelBodyLikeProps {
12
+ children?: ReactNode;
13
+ initialOpen?: boolean;
14
+ title?: ReactNode;
15
+ }
16
+ export interface ToggleControlLikeProps {
17
+ checked?: boolean;
18
+ help?: ReactNode;
19
+ label?: ReactNode;
20
+ onChange?: (value: boolean) => void;
21
+ }
22
+ export interface SelectControlLikeProps {
23
+ help?: ReactNode;
24
+ label?: ReactNode;
25
+ onChange?: (value: string) => void;
26
+ options?: readonly InspectorSelectOption[];
27
+ value?: string;
28
+ }
29
+ export interface RangeControlLikeProps {
30
+ help?: ReactNode;
31
+ label?: ReactNode;
32
+ max?: number;
33
+ min?: number;
34
+ onChange?: (value?: number) => void;
35
+ step?: number;
36
+ value?: number;
37
+ }
38
+ export interface TextControlLikeProps {
39
+ help?: ReactNode;
40
+ label?: ReactNode;
41
+ max?: number;
42
+ min?: number;
43
+ onBlur?: () => void;
44
+ onChange?: (value: string) => void;
45
+ step?: number;
46
+ type?: string;
47
+ value?: string;
48
+ }
49
+ export interface TextareaControlLikeProps {
50
+ help?: ReactNode;
51
+ label?: ReactNode;
52
+ onChange?: (value: string) => void;
53
+ value?: string;
54
+ }
55
+ export interface InspectorComponentMap {
56
+ PanelBody?: React.ElementType<PanelBodyLikeProps>;
57
+ RangeControl?: React.ElementType<RangeControlLikeProps>;
58
+ SelectControl?: React.ElementType<SelectControlLikeProps>;
59
+ TextControl?: React.ElementType<TextControlLikeProps>;
60
+ TextareaControl?: React.ElementType<TextareaControlLikeProps>;
61
+ ToggleControl?: React.ElementType<ToggleControlLikeProps>;
62
+ }
63
+ export interface UseEditorFieldsResult {
64
+ fields: EditorFieldDescriptor[];
65
+ fieldMap: Map<string, EditorFieldDescriptor>;
66
+ getBooleanValue: (source: UnknownRecord, path: string, fallback: boolean) => boolean;
67
+ getField: (path: string) => EditorFieldDescriptor | undefined;
68
+ getNumberValue: (source: UnknownRecord, path: string, fallback: number) => number;
69
+ getSelectOptions: (path: string, labelMap?: Record<string, string>) => InspectorSelectOption[];
70
+ getStringValue: (source: UnknownRecord, path: string, fallback: string) => string;
71
+ manualFields: EditorFieldDescriptor[];
72
+ supportedFields: EditorFieldDescriptor[];
73
+ }
74
+ export interface TypedAttributeUpdater<T extends object> {
75
+ updateAttribute: <K extends keyof T>(key: K, value: T[K]) => boolean;
76
+ updateField: <K extends keyof T>(path: K | string, value: unknown) => boolean;
77
+ updatePath: (path: string, value: unknown) => boolean;
78
+ }
79
+ export interface FieldControlRenderContext {
80
+ components: InspectorComponentMap;
81
+ field: EditorFieldDescriptor;
82
+ help?: ReactNode;
83
+ label?: ReactNode;
84
+ max?: number;
85
+ min?: number;
86
+ onChange: (value: unknown) => void;
87
+ options?: readonly InspectorSelectOption[];
88
+ step?: number;
89
+ value: unknown;
90
+ }
91
+ export interface FieldControlProps {
92
+ components?: InspectorComponentMap;
93
+ field: EditorFieldDescriptor;
94
+ help?: ReactNode;
95
+ label?: ReactNode;
96
+ max?: number;
97
+ min?: number;
98
+ onChange: (value: unknown) => void;
99
+ options?: readonly InspectorSelectOption[];
100
+ render?: (context: FieldControlRenderContext) => ReactNode;
101
+ renderUnsupported?: (context: FieldControlRenderContext) => ReactNode;
102
+ step?: number;
103
+ value: unknown;
104
+ }
105
+ export interface InspectorFieldOverride {
106
+ help?: ReactNode;
107
+ label?: ReactNode;
108
+ max?: number;
109
+ min?: number;
110
+ options?: readonly InspectorSelectOption[];
111
+ render?: (context: FieldControlRenderContext) => ReactNode;
112
+ renderUnsupported?: (context: FieldControlRenderContext) => ReactNode;
113
+ step?: number;
114
+ }
115
+ export interface InspectorFromManifestProps<T extends UnknownRecord> {
116
+ attributes: T;
117
+ children?: ReactNode;
118
+ components?: InspectorComponentMap;
119
+ fieldLookup: UseEditorFieldsResult;
120
+ fieldOverrides?: Record<string, InspectorFieldOverride>;
121
+ initialOpen?: boolean;
122
+ onChange: (path: string, value: unknown) => void;
123
+ paths: readonly string[];
124
+ title?: ReactNode;
125
+ }
126
+ export declare function useEditorFields(manifest: ManifestDocument, options?: EditorModelOptions): UseEditorFieldsResult;
127
+ export declare function useTypedAttributeUpdater<T extends object>(attributes: T, setAttributes: (attrs: Partial<T>) => void, validate?: (value: T) => ValidationResult<T>): TypedAttributeUpdater<T>;
128
+ export declare function FieldControl({ components, field, help, label, max, min, onChange, options, render, renderUnsupported, step, value, }: FieldControlProps): import("react/jsx-runtime.js").JSX.Element | null;
129
+ export declare function InspectorFromManifest<T extends UnknownRecord>({ attributes, children, components, fieldLookup, fieldOverrides, initialOpen, onChange, paths, title, }: InspectorFromManifestProps<T>): import("react/jsx-runtime.js").JSX.Element;
@@ -0,0 +1,283 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useState, } from "react";
3
+ import { createEditorModel, } from "./editor.js";
4
+ import { createAttributeUpdater, createNestedAttributeUpdater, } from "./validation.js";
5
+ import { isPlainObject as isRecord } from "./object-utils.js";
6
+ function getPathSegments(path) {
7
+ return path.split(".").filter(Boolean);
8
+ }
9
+ function getDefaultValue(field, fallback) {
10
+ if (field?.hasDefault) {
11
+ return field.defaultValue;
12
+ }
13
+ return fallback;
14
+ }
15
+ function getValueAtPath(source, path) {
16
+ return getPathSegments(path).reduce((current, segment) => {
17
+ if (!isRecord(current)) {
18
+ return undefined;
19
+ }
20
+ return current[segment];
21
+ }, source);
22
+ }
23
+ function toStringValue(value, fallback) {
24
+ return typeof value === "string" ? value : fallback;
25
+ }
26
+ function toNumberValue(value, fallback) {
27
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
28
+ }
29
+ function toBooleanValue(value, fallback) {
30
+ return typeof value === "boolean" ? value : fallback;
31
+ }
32
+ function toSelectOptions(options, labelMap) {
33
+ return options.map((option) => ({
34
+ label: labelMap?.[String(option.value)] ?? option.label,
35
+ value: String(option.value),
36
+ }));
37
+ }
38
+ function createValidationResult(value) {
39
+ return {
40
+ data: value,
41
+ errors: [],
42
+ isValid: true,
43
+ };
44
+ }
45
+ function asComponent(value) {
46
+ if (typeof value === "function" ||
47
+ (value !== null && typeof value === "object")) {
48
+ return value;
49
+ }
50
+ return undefined;
51
+ }
52
+ function FallbackPanelBody({ children }) {
53
+ return _jsx(_Fragment, { children: children });
54
+ }
55
+ function getGlobalInspectorComponents() {
56
+ const globalScope = globalThis;
57
+ const componentRegistry = globalScope.wp?.components ?? globalScope.window?.wp?.components ?? {};
58
+ return {
59
+ PanelBody: asComponent(componentRegistry.PanelBody),
60
+ RangeControl: asComponent(componentRegistry.RangeControl),
61
+ SelectControl: asComponent(componentRegistry.SelectControl),
62
+ TextControl: asComponent(componentRegistry.TextControl),
63
+ TextareaControl: asComponent(componentRegistry.TextareaControl),
64
+ ToggleControl: asComponent(componentRegistry.ToggleControl),
65
+ };
66
+ }
67
+ function resolveInspectorComponents(components) {
68
+ return {
69
+ ...getGlobalInspectorComponents(),
70
+ ...(components ?? {}),
71
+ };
72
+ }
73
+ function getFieldValue(field, source) {
74
+ const currentValue = getValueAtPath(source, field.path);
75
+ if (currentValue !== undefined) {
76
+ return currentValue;
77
+ }
78
+ switch (field.control) {
79
+ case "toggle":
80
+ return getDefaultValue(field, false);
81
+ case "number":
82
+ case "range":
83
+ return getDefaultValue(field, 0);
84
+ case "select":
85
+ case "text":
86
+ case "textarea":
87
+ return getDefaultValue(field, "");
88
+ default:
89
+ return getDefaultValue(field, undefined);
90
+ }
91
+ }
92
+ function getFieldControlContext(field, props, resolvedComponents) {
93
+ return {
94
+ components: resolvedComponents,
95
+ field,
96
+ help: props.help,
97
+ label: props.label ?? field.label,
98
+ max: props.max ?? field.maximum ?? undefined,
99
+ min: props.min ?? field.minimum ?? undefined,
100
+ onChange: props.onChange,
101
+ options: props.options,
102
+ step: props.step ?? field.step ?? undefined,
103
+ value: props.value,
104
+ };
105
+ }
106
+ function parseSelectValue(field, value, overrideOptions) {
107
+ const matchedOption = field.options.find((option) => String(option.value) === value);
108
+ if (matchedOption) {
109
+ return matchedOption.value;
110
+ }
111
+ if (overrideOptions?.some((option) => option.value === value)) {
112
+ return value;
113
+ }
114
+ return value;
115
+ }
116
+ function formatNumberDraft(value) {
117
+ return String(toNumberValue(value, 0));
118
+ }
119
+ function parseNumberDraft(value) {
120
+ const trimmed = value.trim();
121
+ if (trimmed.length === 0) {
122
+ return undefined;
123
+ }
124
+ const parsed = Number(trimmed);
125
+ return Number.isFinite(parsed) ? parsed : undefined;
126
+ }
127
+ function NumberFieldControl({ context, onChange, value, TextControl, }) {
128
+ const committedDraft = formatNumberDraft(value);
129
+ const [draft, setDraft] = useState(committedDraft);
130
+ useEffect(() => {
131
+ setDraft(committedDraft);
132
+ }, [committedDraft]);
133
+ const commitDraft = useCallback(() => {
134
+ const parsed = parseNumberDraft(draft);
135
+ if (parsed === undefined) {
136
+ setDraft(committedDraft);
137
+ return;
138
+ }
139
+ onChange(parsed);
140
+ setDraft(String(parsed));
141
+ }, [committedDraft, draft, onChange]);
142
+ return (_jsx(TextControl, { help: context.help, label: context.label, max: context.max, min: context.min, onBlur: commitDraft, onChange: setDraft, step: context.step, type: "number", value: draft }));
143
+ }
144
+ export function useEditorFields(manifest, options = {}) {
145
+ const optionsKey = JSON.stringify({
146
+ hidden: options.hidden ?? [],
147
+ labels: options.labels ?? {},
148
+ manual: options.manual ?? [],
149
+ preferTextarea: options.preferTextarea ?? [],
150
+ });
151
+ const stableOptions = useMemo(() => JSON.parse(optionsKey), [optionsKey]);
152
+ const fields = useMemo(() => createEditorModel(manifest, stableOptions), [manifest, stableOptions]);
153
+ const fieldMap = useMemo(() => new Map(fields.map((field) => [field.path, field])), [fields]);
154
+ const supportedFields = useMemo(() => fields.filter((field) => field.supported), [fields]);
155
+ const manualFields = useMemo(() => fields.filter((field) => !field.supported), [fields]);
156
+ const getField = (path) => fieldMap.get(path);
157
+ const getStringValue = (source, path, fallback) => toStringValue(getValueAtPath(source, path) ?? getDefaultValue(getField(path), fallback), fallback);
158
+ const getNumberValue = (source, path, fallback) => toNumberValue(getValueAtPath(source, path) ?? getDefaultValue(getField(path), fallback), fallback);
159
+ const getBooleanValue = (source, path, fallback) => toBooleanValue(getValueAtPath(source, path) ?? getDefaultValue(getField(path), fallback), fallback);
160
+ const getSelectOptions = (path, labelMap) => toSelectOptions(getField(path)?.options ?? [], labelMap);
161
+ return {
162
+ fields,
163
+ fieldMap,
164
+ getBooleanValue,
165
+ getField,
166
+ getNumberValue,
167
+ getSelectOptions,
168
+ getStringValue,
169
+ manualFields,
170
+ supportedFields,
171
+ };
172
+ }
173
+ export function useTypedAttributeUpdater(attributes, setAttributes, validate) {
174
+ const validateAttributes = useMemo(() => validate ?? ((value) => createValidationResult(value)), [validate]);
175
+ const updateAttribute = useMemo(() => createAttributeUpdater(attributes, setAttributes, validateAttributes), [attributes, setAttributes, validateAttributes]);
176
+ const updatePath = useMemo(() => createNestedAttributeUpdater(attributes, setAttributes, validateAttributes), [attributes, setAttributes, validateAttributes]);
177
+ const updateField = useCallback((path, value) => {
178
+ if (typeof path === "string" && path.includes(".")) {
179
+ return updatePath(path, value);
180
+ }
181
+ return updateAttribute(path, value);
182
+ }, [updateAttribute, updatePath]);
183
+ return {
184
+ updateAttribute,
185
+ updateField,
186
+ updatePath,
187
+ };
188
+ }
189
+ export function FieldControl({ components, field, help, label, max, min, onChange, options, render, renderUnsupported, step, value, }) {
190
+ const resolvedComponents = resolveInspectorComponents(components);
191
+ const context = getFieldControlContext(field, {
192
+ help,
193
+ label,
194
+ max,
195
+ min,
196
+ onChange,
197
+ options,
198
+ step,
199
+ value,
200
+ }, resolvedComponents);
201
+ if (render) {
202
+ return _jsx(_Fragment, { children: render(context) });
203
+ }
204
+ if (!field.supported) {
205
+ return renderUnsupported ? _jsx(_Fragment, { children: renderUnsupported(context) }) : null;
206
+ }
207
+ switch (field.control) {
208
+ case "toggle": {
209
+ const ToggleControl = resolvedComponents.ToggleControl;
210
+ if (!ToggleControl) {
211
+ return null;
212
+ }
213
+ return (_jsx(ToggleControl, { checked: toBooleanValue(value, false), help: context.help, label: context.label, onChange: (nextValue) => {
214
+ onChange(nextValue);
215
+ } }));
216
+ }
217
+ case "select": {
218
+ const SelectControl = resolvedComponents.SelectControl;
219
+ if (!SelectControl) {
220
+ return null;
221
+ }
222
+ const selectOptions = context.options ?? toSelectOptions(field.options);
223
+ return (_jsx(SelectControl, { help: context.help, label: context.label, onChange: (nextValue) => {
224
+ onChange(parseSelectValue(field, nextValue, context.options));
225
+ }, options: selectOptions, value: String(value ?? "") }));
226
+ }
227
+ case "range": {
228
+ const RangeControl = resolvedComponents.RangeControl;
229
+ if (!RangeControl) {
230
+ return null;
231
+ }
232
+ return (_jsx(RangeControl, { help: context.help, label: context.label, max: context.max, min: context.min, onChange: (nextValue) => {
233
+ if (typeof nextValue === "number" && Number.isFinite(nextValue)) {
234
+ onChange(nextValue);
235
+ }
236
+ }, step: context.step, value: toNumberValue(value, 0) }));
237
+ }
238
+ case "number": {
239
+ const TextControl = resolvedComponents.TextControl;
240
+ if (!TextControl) {
241
+ return null;
242
+ }
243
+ return (_jsx(NumberFieldControl, { context: context, onChange: onChange, TextControl: TextControl, value: value }));
244
+ }
245
+ case "textarea": {
246
+ const TextareaControl = resolvedComponents.TextareaControl;
247
+ if (!TextareaControl) {
248
+ return null;
249
+ }
250
+ return (_jsx(TextareaControl, { help: context.help, label: context.label, onChange: (nextValue) => {
251
+ onChange(nextValue);
252
+ }, value: toStringValue(value, "") }));
253
+ }
254
+ case "text": {
255
+ const TextControl = resolvedComponents.TextControl;
256
+ if (!TextControl) {
257
+ return null;
258
+ }
259
+ return (_jsx(TextControl, { help: context.help, label: context.label, onChange: (nextValue) => {
260
+ onChange(nextValue);
261
+ }, value: toStringValue(value, "") }));
262
+ }
263
+ default:
264
+ return renderUnsupported ? _jsx(_Fragment, { children: renderUnsupported(context) }) : null;
265
+ }
266
+ }
267
+ export function InspectorFromManifest({ attributes, children, components, fieldLookup, fieldOverrides, initialOpen, onChange, paths, title, }) {
268
+ const resolvedComponents = resolveInspectorComponents(components);
269
+ const PanelBody = resolvedComponents.PanelBody ?? FallbackPanelBody;
270
+ const fieldControls = paths
271
+ .map((path) => {
272
+ const field = fieldLookup.getField(path);
273
+ if (!field) {
274
+ return null;
275
+ }
276
+ const overrides = fieldOverrides?.[path];
277
+ return (_jsx(FieldControl, { components: resolvedComponents, field: field, help: overrides?.help, label: overrides?.label, max: overrides?.max, min: overrides?.min, onChange: (value) => {
278
+ onChange(path, value);
279
+ }, options: overrides?.options, render: overrides?.render, renderUnsupported: overrides?.renderUnsupported, step: overrides?.step, value: getFieldValue(field, attributes) }, path));
280
+ })
281
+ .filter(Boolean);
282
+ return (_jsxs(PanelBody, { initialOpen: initialOpen, title: title, children: [fieldControls, children] }));
283
+ }
@@ -1 +1 @@
1
- export * from "@wp-typia/create/runtime/inspector";
1
+ export * from "./inspector-runtime.js";
package/dist/inspector.js CHANGED
@@ -1 +1 @@
1
- export * from "@wp-typia/create/runtime/inspector";
1
+ export * from "./inspector-runtime.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Create a deep clone of a JSON-serializable value.
3
+ *
4
+ * @param value JSON-compatible data to clone.
5
+ * @returns A deep-cloned copy created with `JSON.parse(JSON.stringify(...))`.
6
+ *
7
+ * Values that are not JSON-serializable, such as functions, `undefined`,
8
+ * `BigInt`, class instances, and `Date` objects, are not preserved faithfully.
9
+ */
10
+ export declare function cloneJsonValue<T>(value: T): T;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Create a deep clone of a JSON-serializable value.
3
+ *
4
+ * @param value JSON-compatible data to clone.
5
+ * @returns A deep-cloned copy created with `JSON.parse(JSON.stringify(...))`.
6
+ *
7
+ * Values that are not JSON-serializable, such as functions, `undefined`,
8
+ * `BigInt`, class instances, and `Date` objects, are not preserved faithfully.
9
+ */
10
+ export function cloneJsonValue(value) {
11
+ return JSON.parse(JSON.stringify(value));
12
+ }
@@ -0,0 +1,11 @@
1
+ import ts from "typescript";
2
+ export interface AnalysisContext {
3
+ allowedExternalPackages: Set<string>;
4
+ checker: ts.TypeChecker;
5
+ packageNameCache: Map<string, string | null>;
6
+ projectRoot: string;
7
+ program: ts.Program;
8
+ recursionGuard: Set<string>;
9
+ }
10
+ export declare function getTaggedSyncBlockMetadataFailureCode(error: Error): "typescript-diagnostic" | undefined;
11
+ export declare function createAnalysisContext(projectRoot: string, typesFilePath: string): AnalysisContext;