@mohasinac/appkit 4.41.3 → 4.41.4
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.
|
@@ -162,8 +162,17 @@ function stringMaxLength(schema) {
|
|
|
162
162
|
*/
|
|
163
163
|
function enumOptions(schema) {
|
|
164
164
|
const direct = schema.options;
|
|
165
|
-
if (Array.isArray(direct))
|
|
166
|
-
|
|
165
|
+
if (Array.isArray(direct)) {
|
|
166
|
+
const strings = direct.filter((o) => typeof o === "string");
|
|
167
|
+
// On a ZodUnion `.options` is the member SCHEMAS, not strings, so the
|
|
168
|
+
// filter above yields []. Empty is truthy downstream, which is how a union
|
|
169
|
+
// rendered as a <select> with nothing in it. Read the literals instead.
|
|
170
|
+
if (strings.length === 0) {
|
|
171
|
+
const literals = unionStringLiterals(direct);
|
|
172
|
+
return literals.length > 0 ? literals : undefined;
|
|
173
|
+
}
|
|
174
|
+
return strings;
|
|
175
|
+
}
|
|
167
176
|
const values = schema._def?.values;
|
|
168
177
|
if (Array.isArray(values))
|
|
169
178
|
return values.filter((o) => typeof o === "string");
|
|
@@ -172,6 +181,26 @@ function enumOptions(schema) {
|
|
|
172
181
|
}
|
|
173
182
|
return undefined;
|
|
174
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* String literal values carried by a union's members.
|
|
186
|
+
*
|
|
187
|
+
* `z.literal("a").or(z.literal("b"))` is a genuine choice and yields
|
|
188
|
+
* `["a", "b"]`. `z.string().optional().or(z.literal(""))` — the idiomatic
|
|
189
|
+
* "optional text that also accepts empty" — yields nothing usable, because its
|
|
190
|
+
* only literal is the empty string. That distinction is what decides whether a
|
|
191
|
+
* union field is a select or a text input.
|
|
192
|
+
*/
|
|
193
|
+
function unionStringLiterals(members) {
|
|
194
|
+
const out = [];
|
|
195
|
+
for (const member of members) {
|
|
196
|
+
const direct = member?.value;
|
|
197
|
+
const nested = member?._def?.value;
|
|
198
|
+
const value = typeof direct === "string" ? direct : nested;
|
|
199
|
+
if (typeof value === "string" && value !== "")
|
|
200
|
+
out.push(value);
|
|
201
|
+
}
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
175
204
|
/** Public: the option list for a select-shaped field, or null. */
|
|
176
205
|
export function schemaEnumOptions(schema) {
|
|
177
206
|
const { schema: inner } = unwrapSchema(schema);
|
|
@@ -180,7 +209,27 @@ export function schemaEnumOptions(schema) {
|
|
|
180
209
|
/** Public: whether a field's type is optional-wrapped (i.e. not required). */
|
|
181
210
|
export function schemaIsOptional(schema) {
|
|
182
211
|
const type = typeOf(schema);
|
|
183
|
-
|
|
212
|
+
if (type === "optional" || type === "default" || type === "nullish")
|
|
213
|
+
return true;
|
|
214
|
+
// A union HIDES optionality from the check above: the outer type is "union",
|
|
215
|
+
// so `z.string().optional().or(z.literal(""))` read as required and rendered
|
|
216
|
+
// a `*` on a field the schema is happy to leave empty. If any member is
|
|
217
|
+
// optional, or accepts "", the field is optional.
|
|
218
|
+
if (type === "union") {
|
|
219
|
+
const members = schema.options;
|
|
220
|
+
if (Array.isArray(members)) {
|
|
221
|
+
return members.some((member) => {
|
|
222
|
+
const memberType = typeOf(member);
|
|
223
|
+
if (memberType === "optional" || memberType === "default" || memberType === "nullish") {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
const direct = member?.value;
|
|
227
|
+
const nested = member?._def?.value;
|
|
228
|
+
return direct === "" || nested === "";
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return false;
|
|
184
233
|
}
|
|
185
234
|
/**
|
|
186
235
|
* The `.shape` of an object schema, seeing through effects wrappers.
|
|
@@ -257,8 +306,19 @@ function fromZodType(type, inner) {
|
|
|
257
306
|
case "enum":
|
|
258
307
|
case "nativeEnum":
|
|
259
308
|
case "literal":
|
|
260
|
-
case "union":
|
|
261
309
|
return { kind: "select", row: "pair" };
|
|
310
|
+
case "union": {
|
|
311
|
+
// Only a union that actually yields choices is a select. The other
|
|
312
|
+
// common shape, `z.string().optional().or(z.literal(""))`, is an optional
|
|
313
|
+
// TEXT field — rendering it as a select produced a dropdown with zero
|
|
314
|
+
// options, which is unusable and (being marked required) made the whole
|
|
315
|
+
// form unsubmittable. Seen live on /user/addresses/new, where
|
|
316
|
+
// "Address line 2" and "Landmark" were both required empty dropdowns.
|
|
317
|
+
const choices = enumOptions(inner);
|
|
318
|
+
return choices && choices.length > 0
|
|
319
|
+
? { kind: "select", row: "pair" }
|
|
320
|
+
: { kind: "text", row: "pair" };
|
|
321
|
+
}
|
|
262
322
|
case "date":
|
|
263
323
|
return { kind: "date", row: "pair" };
|
|
264
324
|
case "number":
|