wp-typia 0.16.1 → 0.16.3

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.
@@ -1,32 +1,95 @@
1
- import { SchemaForm } from "@bunli/tui";
2
- import { HOOKED_BLOCK_POSITION_IDS } from "@wp-typia/project-tools";
3
- import { z } from "zod";
1
+ import { createElement, useEffect, useMemo, useState } from "react";
4
2
 
5
- import { executeAddCommand } from "../runtime-bridge";
3
+ import {
4
+ Form,
5
+ type SelectOption,
6
+ useFormContext,
7
+ useTerminalDimensions,
8
+ } from "@bunli/tui";
9
+ import { HOOKED_BLOCK_POSITION_IDS } from "@wp-typia/project-tools/hooked-blocks";
10
+
11
+ import { executeAddCommand, loadAddWorkspaceBlockOptions } from "../runtime-bridge";
6
12
  import { useAlternateBufferLifecycle } from "./alternate-buffer-lifecycle";
13
+ import {
14
+ type AddFlowValues,
15
+ addFlowSchema,
16
+ getAddScrollTop,
17
+ getAddViewportHeight,
18
+ getVisibleAddFieldNames,
19
+ isAddPersistenceTemplate,
20
+ sanitizeAddSubmitValues,
21
+ } from "./add-flow-model";
22
+ import {
23
+ FirstPartyScrollBox,
24
+ FirstPartySelectField,
25
+ FirstPartyTextField,
26
+ } from "./first-party-form";
27
+ import { getWrappedFieldNeighbors } from "./first-party-form-model";
7
28
 
8
- const addFlowSchema = z.object({
9
- anchor: z.string().optional(),
10
- block: z.string().optional(),
11
- "data-storage": z.string().optional(),
12
- kind: z.enum(["block", "variation", "pattern", "binding-source", "hooked-block"]).default("block"),
13
- name: z.string().optional(),
14
- "persistence-policy": z.string().optional(),
15
- position: z.string().optional(),
16
- template: z.string().optional(),
17
- });
29
+ const kindOptions: SelectOption[] = [
30
+ { name: "block", description: "Add a real block slice", value: "block" },
31
+ {
32
+ name: "variation",
33
+ description: "Add a variation to an existing block",
34
+ value: "variation",
35
+ },
36
+ {
37
+ name: "pattern",
38
+ description: "Add a PHP block pattern shell",
39
+ value: "pattern",
40
+ },
41
+ {
42
+ name: "binding-source",
43
+ description: "Add a shared block bindings source",
44
+ value: "binding-source",
45
+ },
46
+ {
47
+ name: "hooked-block",
48
+ description: "Add block.json hook metadata to an existing block",
49
+ value: "hooked-block",
50
+ },
51
+ ];
18
52
 
19
- type AddFlowValues = z.infer<typeof addFlowSchema>;
53
+ const templateOptions: SelectOption[] = [
54
+ { name: "basic", description: "Basic block scaffold", value: "basic" },
55
+ {
56
+ name: "interactivity",
57
+ description: "Interactivity API block scaffold",
58
+ value: "interactivity",
59
+ },
60
+ {
61
+ name: "persistence",
62
+ description: "Persistence-enabled block scaffold",
63
+ value: "persistence",
64
+ },
65
+ {
66
+ name: "compound",
67
+ description: "Compound parent + child scaffold",
68
+ value: "compound",
69
+ },
70
+ ];
20
71
 
21
- type AddFlowProps = {
22
- cwd: string;
23
- initialValues: Partial<AddFlowValues>;
24
- workspaceBlockOptions: Array<{
25
- description: string;
26
- name: string;
27
- value: string;
28
- }>;
29
- };
72
+ const dataStorageOptions: SelectOption[] = [
73
+ {
74
+ name: "custom-table",
75
+ description: "Dedicated custom table storage",
76
+ value: "custom-table",
77
+ },
78
+ {
79
+ name: "post-meta",
80
+ description: "Persist through post meta",
81
+ value: "post-meta",
82
+ },
83
+ ];
84
+
85
+ const persistencePolicyOptions: SelectOption[] = [
86
+ {
87
+ name: "authenticated",
88
+ description: "Authenticated write policy",
89
+ value: "authenticated",
90
+ },
91
+ { name: "public", description: "Public token policy", value: "public" },
92
+ ];
30
93
 
31
94
  const HOOKED_BLOCK_POSITION_DESCRIPTIONS: Record<
32
95
  (typeof HOOKED_BLOCK_POSITION_IDS)[number],
@@ -38,170 +101,207 @@ const HOOKED_BLOCK_POSITION_DESCRIPTIONS: Record<
38
101
  lastChild: "Insert as the last child of the anchor block",
39
102
  };
40
103
 
41
- export function AddFlow({ cwd, initialValues, workspaceBlockOptions }: AddFlowProps) {
42
- const { handleCancel, handleSubmit } = useAlternateBufferLifecycle("wp-typia add failed");
104
+ type AddFlowProps = {
105
+ cwd: string;
106
+ initialValues: Partial<AddFlowValues>;
107
+ };
108
+
109
+ type WorkspaceBlockOption = {
110
+ description: string;
111
+ name: string;
112
+ value: string;
113
+ };
43
114
 
44
- return (
45
- <SchemaForm
46
- fields={[
47
- {
48
- kind: "select",
49
- label: "Kind",
50
- name: "kind",
51
- options: [
52
- { name: "block", description: "Add a real block slice", value: "block" },
53
- {
54
- name: "variation",
55
- description: "Add a variation to an existing block",
56
- value: "variation",
57
- },
58
- {
59
- name: "pattern",
60
- description: "Add a PHP block pattern shell",
61
- value: "pattern",
62
- },
63
- {
64
- name: "binding-source",
65
- description: "Add a shared block bindings source",
66
- value: "binding-source",
67
- },
68
- {
69
- name: "hooked-block",
70
- description: "Add block.json hook metadata to an existing block",
71
- value: "hooked-block",
72
- },
73
- ],
74
- },
75
- {
76
- kind: "text",
77
- label: "Block name",
115
+ type AddSelectFieldName = {
116
+ [K in keyof AddFlowValues]-?: AddFlowValues[K] extends string | undefined ? K : never;
117
+ }[keyof AddFlowValues];
118
+
119
+ function getAddNameLabel(kind?: string): string {
120
+ switch (kind) {
121
+ case "variation":
122
+ return "Variation name";
123
+ case "pattern":
124
+ return "Pattern name";
125
+ case "binding-source":
126
+ return "Binding source name";
127
+ case "hooked-block":
128
+ return "Target block";
129
+ case "block":
130
+ default:
131
+ return "Block name";
132
+ }
133
+ }
134
+
135
+ function AddFlowFields({
136
+ workspaceBlockOptions,
137
+ }: {
138
+ workspaceBlockOptions: WorkspaceBlockOption[];
139
+ }) {
140
+ const { activeFieldName, values } = useFormContext();
141
+ const { height: terminalHeight = 24 } = useTerminalDimensions();
142
+ const addValues = values as Partial<AddFlowValues>;
143
+ const kind = addValues.kind ?? "block";
144
+ const template = addValues.template;
145
+ const viewportHeight = getAddViewportHeight(terminalHeight);
146
+ const scrollValues = useMemo(() => ({ kind, template }), [kind, template]);
147
+ const scrollTop = useMemo(
148
+ () =>
149
+ getAddScrollTop({
150
+ activeFieldName,
151
+ values: scrollValues,
152
+ viewportHeight,
153
+ }),
154
+ [activeFieldName, scrollValues, viewportHeight],
155
+ );
156
+
157
+ const visibleFields = new Set(getVisibleAddFieldNames(addValues));
158
+ const orderedVisibleFields = useMemo(() => getVisibleAddFieldNames(addValues), [addValues]);
159
+ const hookedBlockNameUsesSelect = kind === "hooked-block" && workspaceBlockOptions.length > 0;
160
+ const variationBlockUsesSelect = kind === "variation" && workspaceBlockOptions.length > 0;
161
+
162
+ return createElement(
163
+ FirstPartyScrollBox,
164
+ { scrollTop, viewportHeight },
165
+ [
166
+ createElement(FirstPartySelectField, {
167
+ ...getWrappedFieldNeighbors(orderedVisibleFields, "kind"),
168
+ key: "kind",
169
+ label: "Kind",
170
+ name: "kind" satisfies AddSelectFieldName,
171
+ options: kindOptions,
172
+ }),
173
+ visibleFields.has("name") && !hookedBlockNameUsesSelect
174
+ ? createElement(FirstPartyTextField, {
175
+ ...getWrappedFieldNeighbors(orderedVisibleFields, "name"),
176
+ key: `name-text:${kind}`,
177
+ label: getAddNameLabel(kind),
78
178
  name: "name",
79
- visibleWhen: (values) => values.kind === "block",
80
- },
81
- {
82
- kind: "select",
179
+ })
180
+ : null,
181
+ hookedBlockNameUsesSelect
182
+ ? createElement(FirstPartySelectField, {
183
+ ...getWrappedFieldNeighbors(orderedVisibleFields, "name"),
184
+ key: "name-select:hooked-block",
185
+ label: getAddNameLabel(kind),
186
+ name: "name" satisfies AddSelectFieldName,
187
+ options: workspaceBlockOptions,
188
+ })
189
+ : null,
190
+ visibleFields.has("template")
191
+ ? createElement(FirstPartySelectField, {
192
+ ...getWrappedFieldNeighbors(orderedVisibleFields, "template"),
193
+ key: "template",
83
194
  label: "Template family",
84
- name: "template",
85
- options: [
86
- { name: "basic", description: "Basic block scaffold", value: "basic" },
87
- {
88
- name: "interactivity",
89
- description: "Interactivity API block scaffold",
90
- value: "interactivity",
91
- },
92
- {
93
- name: "persistence",
94
- description: "Persistence-enabled block scaffold",
95
- value: "persistence",
96
- },
97
- {
98
- name: "compound",
99
- description: "Compound parent + child scaffold",
100
- value: "compound",
101
- },
102
- ],
103
- visibleWhen: (values) => values.kind === "block",
104
- },
105
- {
106
- kind: "text",
107
- label: "Variation name",
108
- name: "name",
109
- visibleWhen: (values) => values.kind === "variation",
110
- },
111
- {
112
- kind: workspaceBlockOptions.length > 0 ? "select" : "text",
195
+ name: "template" satisfies AddSelectFieldName,
196
+ options: templateOptions,
197
+ })
198
+ : null,
199
+ visibleFields.has("block") && !variationBlockUsesSelect
200
+ ? createElement(FirstPartyTextField, {
201
+ ...getWrappedFieldNeighbors(orderedVisibleFields, "block"),
202
+ key: "block-text",
113
203
  label: "Target block",
114
204
  name: "block",
115
- options: workspaceBlockOptions,
116
- visibleWhen: (values) => values.kind === "variation",
117
- },
118
- {
119
- kind: "text",
120
- label: "Pattern name",
121
- name: "name",
122
- visibleWhen: (values) => values.kind === "pattern",
123
- },
124
- {
125
- kind: "text",
126
- label: "Binding source name",
127
- name: "name",
128
- visibleWhen: (values) => values.kind === "binding-source",
129
- },
130
- {
131
- kind: workspaceBlockOptions.length > 0 ? "select" : "text",
205
+ })
206
+ : null,
207
+ variationBlockUsesSelect
208
+ ? createElement(FirstPartySelectField, {
209
+ ...getWrappedFieldNeighbors(orderedVisibleFields, "block"),
210
+ key: "block-select",
132
211
  label: "Target block",
133
- name: "name",
212
+ name: "block" satisfies AddSelectFieldName,
134
213
  options: workspaceBlockOptions,
135
- visibleWhen: (values) => values.kind === "hooked-block",
136
- },
137
- {
138
- kind: "text",
214
+ })
215
+ : null,
216
+ visibleFields.has("anchor")
217
+ ? createElement(FirstPartyTextField, {
218
+ ...getWrappedFieldNeighbors(orderedVisibleFields, "anchor"),
219
+ key: "anchor",
139
220
  label: "Anchor block name",
140
221
  name: "anchor",
141
- visibleWhen: (values) => values.kind === "hooked-block",
142
- },
143
- {
144
- kind: "select",
222
+ })
223
+ : null,
224
+ visibleFields.has("position")
225
+ ? createElement(FirstPartySelectField, {
226
+ ...getWrappedFieldNeighbors(orderedVisibleFields, "position"),
227
+ key: "position",
145
228
  label: "Hook position",
146
- name: "position",
229
+ name: "position" satisfies AddSelectFieldName,
147
230
  options: HOOKED_BLOCK_POSITION_IDS.map((position) => ({
148
- name: position,
149
231
  description: HOOKED_BLOCK_POSITION_DESCRIPTIONS[position],
232
+ name: position,
150
233
  value: position,
151
234
  })),
152
- visibleWhen: (values) => values.kind === "hooked-block",
153
- },
154
- {
155
- kind: "select",
235
+ })
236
+ : null,
237
+ visibleFields.has("data-storage") && isAddPersistenceTemplate(template)
238
+ ? createElement(FirstPartySelectField, {
239
+ ...getWrappedFieldNeighbors(orderedVisibleFields, "data-storage"),
240
+ key: "data-storage",
156
241
  label: "Data storage",
157
- name: "data-storage",
158
- options: [
159
- {
160
- name: "custom-table",
161
- description: "Dedicated custom table storage",
162
- value: "custom-table",
163
- },
164
- {
165
- name: "post-meta",
166
- description: "Persist through post meta",
167
- value: "post-meta",
168
- },
169
- ],
170
- visibleWhen: (values) =>
171
- values.kind === "block" &&
172
- (values.template === "persistence" || values.template === "compound"),
173
- },
174
- {
175
- kind: "select",
242
+ name: "data-storage" satisfies AddSelectFieldName,
243
+ options: dataStorageOptions,
244
+ })
245
+ : null,
246
+ visibleFields.has("persistence-policy") && isAddPersistenceTemplate(template)
247
+ ? createElement(FirstPartySelectField, {
248
+ ...getWrappedFieldNeighbors(orderedVisibleFields, "persistence-policy"),
249
+ key: "persistence-policy",
176
250
  label: "Persistence policy",
177
- name: "persistence-policy",
178
- options: [
179
- {
180
- name: "authenticated",
181
- description: "Authenticated write policy",
182
- value: "authenticated",
183
- },
184
- { name: "public", description: "Public token policy", value: "public" },
185
- ],
186
- visibleWhen: (values) =>
187
- values.kind === "block" &&
188
- (values.template === "persistence" || values.template === "compound"),
189
- },
190
- ]}
251
+ name: "persistence-policy" satisfies AddSelectFieldName,
252
+ options: persistencePolicyOptions,
253
+ })
254
+ : null,
255
+ ],
256
+ );
257
+ }
258
+
259
+ export function AddFlow({ cwd, initialValues }: AddFlowProps) {
260
+ const { handleCancel, handleFailure, handleSubmit } = useAlternateBufferLifecycle(
261
+ "wp-typia add failed",
262
+ );
263
+ const [workspaceBlockOptions, setWorkspaceBlockOptions] = useState<WorkspaceBlockOption[]>([]);
264
+
265
+ useEffect(() => {
266
+ let disposed = false;
267
+ setWorkspaceBlockOptions([]);
268
+
269
+ void loadAddWorkspaceBlockOptions(cwd)
270
+ .then((options) => {
271
+ if (!disposed) {
272
+ setWorkspaceBlockOptions(options);
273
+ }
274
+ })
275
+ .catch((error) => {
276
+ if (!disposed) {
277
+ handleFailure(error);
278
+ }
279
+ });
280
+
281
+ return () => {
282
+ disposed = true;
283
+ };
284
+ }, [cwd, handleFailure]);
285
+
286
+ return (
287
+ <Form
191
288
  initialValues={initialValues}
192
289
  onCancel={handleCancel}
193
290
  onSubmit={async (values) =>
194
291
  handleSubmit(async () => {
195
- await executeAddCommand({
196
- cwd,
197
- flags: values,
198
- kind: values.kind,
199
- name: values.name,
200
- });
292
+ const flags = sanitizeAddSubmitValues(values);
293
+ await executeAddCommand({
294
+ cwd,
295
+ flags,
296
+ kind: values.kind,
297
+ name: typeof flags.name === "string" ? flags.name : undefined,
298
+ });
201
299
  })
202
300
  }
203
301
  schema={addFlowSchema}
204
302
  title="Extend a wp-typia workspace"
205
- />
303
+ >
304
+ <AddFlowFields workspaceBlockOptions={workspaceBlockOptions} />
305
+ </Form>
206
306
  );
207
307
  }
@@ -0,0 +1,125 @@
1
+ import { z } from "zod";
2
+
3
+ import {
4
+ FIRST_PARTY_CHECKBOX_FIELD_BODY_HEIGHT,
5
+ FIRST_PARTY_SELECT_FIELD_BODY_HEIGHT,
6
+ FIRST_PARTY_TEXT_FIELD_BODY_HEIGHT,
7
+ getFirstPartyScrollTop,
8
+ getFirstPartyViewportHeight,
9
+ } from "./first-party-form-model";
10
+
11
+ export const createFlowSchema = z.object({
12
+ "data-storage": z.string().optional(),
13
+ namespace: z.string().optional(),
14
+ "no-install": z.boolean().default(false),
15
+ "package-manager": z.string().optional(),
16
+ "persistence-policy": z.string().optional(),
17
+ "php-prefix": z.string().optional(),
18
+ "project-dir": z.string().min(1),
19
+ template: z.string().optional(),
20
+ "text-domain": z.string().optional(),
21
+ variant: z.string().optional(),
22
+ "with-migration-ui": z.boolean().default(false),
23
+ "with-test-preset": z.boolean().default(false),
24
+ "with-wp-env": z.boolean().default(false),
25
+ yes: z.boolean().default(false),
26
+ });
27
+
28
+ export type CreateFlowValues = z.infer<typeof createFlowSchema>;
29
+
30
+ export type CreateFieldName =
31
+ | "project-dir"
32
+ | "template"
33
+ | "package-manager"
34
+ | "namespace"
35
+ | "text-domain"
36
+ | "php-prefix"
37
+ | "data-storage"
38
+ | "persistence-policy"
39
+ | "no-install"
40
+ | "yes"
41
+ | "with-wp-env"
42
+ | "with-test-preset"
43
+ | "with-migration-ui";
44
+
45
+ export const CREATE_CHECKBOX_FIELD_NAMES = [
46
+ "no-install",
47
+ "yes",
48
+ "with-wp-env",
49
+ "with-test-preset",
50
+ "with-migration-ui",
51
+ ] as const satisfies ReadonlyArray<CreateFieldName>;
52
+
53
+ export const CREATE_FIELD_ORDER = [
54
+ "project-dir",
55
+ "template",
56
+ "package-manager",
57
+ "namespace",
58
+ "text-domain",
59
+ "php-prefix",
60
+ "data-storage",
61
+ "persistence-policy",
62
+ ...CREATE_CHECKBOX_FIELD_NAMES,
63
+ ] as const satisfies ReadonlyArray<CreateFieldName>;
64
+
65
+ const CREATE_FIELD_HEIGHTS: Record<CreateFieldName, number> = {
66
+ "data-storage": FIRST_PARTY_SELECT_FIELD_BODY_HEIGHT,
67
+ namespace: FIRST_PARTY_TEXT_FIELD_BODY_HEIGHT,
68
+ "no-install": FIRST_PARTY_CHECKBOX_FIELD_BODY_HEIGHT,
69
+ "package-manager": FIRST_PARTY_SELECT_FIELD_BODY_HEIGHT,
70
+ "persistence-policy": FIRST_PARTY_SELECT_FIELD_BODY_HEIGHT,
71
+ "php-prefix": FIRST_PARTY_TEXT_FIELD_BODY_HEIGHT,
72
+ "project-dir": FIRST_PARTY_TEXT_FIELD_BODY_HEIGHT,
73
+ template: FIRST_PARTY_SELECT_FIELD_BODY_HEIGHT,
74
+ "text-domain": FIRST_PARTY_TEXT_FIELD_BODY_HEIGHT,
75
+ yes: FIRST_PARTY_CHECKBOX_FIELD_BODY_HEIGHT,
76
+ "with-migration-ui": FIRST_PARTY_CHECKBOX_FIELD_BODY_HEIGHT,
77
+ "with-test-preset": FIRST_PARTY_CHECKBOX_FIELD_BODY_HEIGHT,
78
+ "with-wp-env": FIRST_PARTY_CHECKBOX_FIELD_BODY_HEIGHT,
79
+ };
80
+
81
+ export function isCreatePersistenceTemplate(template?: string): boolean {
82
+ return template === "persistence" || template === "compound";
83
+ }
84
+
85
+ export function getVisibleCreateFieldNames(
86
+ values: Partial<CreateFlowValues>,
87
+ ): Array<CreateFieldName> {
88
+ return CREATE_FIELD_ORDER.filter((name) => {
89
+ if (name === "data-storage" || name === "persistence-policy") {
90
+ return isCreatePersistenceTemplate(values.template);
91
+ }
92
+
93
+ return true;
94
+ });
95
+ }
96
+
97
+ export function getCreateViewportHeight(terminalHeight = 24): number {
98
+ return getFirstPartyViewportHeight(terminalHeight);
99
+ }
100
+
101
+ export function getCreateScrollTop(options: {
102
+ activeFieldName: string | null;
103
+ values: Partial<CreateFlowValues>;
104
+ viewportHeight: number;
105
+ }): number {
106
+ const { activeFieldName, values, viewportHeight } = options;
107
+ return getFirstPartyScrollTop({
108
+ activeFieldName,
109
+ fieldHeights: CREATE_FIELD_HEIGHTS,
110
+ visibleFieldNames: getVisibleCreateFieldNames(values),
111
+ viewportHeight,
112
+ });
113
+ }
114
+
115
+ export function sanitizeCreateSubmitValues(values: CreateFlowValues): CreateFlowValues {
116
+ if (isCreatePersistenceTemplate(values.template)) {
117
+ return values;
118
+ }
119
+
120
+ return {
121
+ ...values,
122
+ "data-storage": undefined,
123
+ "persistence-policy": undefined,
124
+ };
125
+ }