@marktiderman/genesis-forms 0.4.2 → 0.6.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 +195 -0
- package/dist/destinations.cjs +147 -7
- package/dist/destinations.cjs.map +1 -1
- package/dist/destinations.d.cts +1 -1
- package/dist/destinations.d.ts +1 -1
- package/dist/destinations.js +147 -7
- package/dist/destinations.js.map +1 -1
- package/dist/{engine-BkNuzlna.d.cts → engine-CpfOz8FN.d.ts} +33 -3
- package/dist/{engine-Dz9J5ej2.d.ts → engine-DC4YTPdr.d.cts} +33 -3
- package/dist/index.cjs +734 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +142 -75
- package/dist/index.d.ts +142 -75
- package/dist/index.js +687 -70
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +721 -26
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +201 -4
- package/dist/server.d.ts +201 -4
- package/dist/server.js +702 -27
- package/dist/server.js.map +1 -1
- package/dist/testing.cjs +494 -26
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +4 -4
- package/dist/testing.d.ts +4 -4
- package/dist/testing.js +494 -26
- package/dist/testing.js.map +1 -1
- package/dist/{types-BBKBQ8sp.d.cts → types-oAckGR_Z.d.cts} +31 -1
- package/dist/{types-BBKBQ8sp.d.ts → types-oAckGR_Z.d.ts} +31 -1
- package/dist/validate-B5WKHbst.d.cts +322 -0
- package/dist/validate-GpTXPwyl.d.ts +322 -0
- package/package.json +1 -1
- package/dist/validate-BXXqi0fq.d.cts +0 -30
- package/dist/validate-BjNwF1UE.d.ts +0 -30
package/dist/destinations.d.cts
CHANGED
package/dist/destinations.d.ts
CHANGED
package/dist/destinations.js
CHANGED
|
@@ -326,6 +326,79 @@ var structuredFieldTypes = [
|
|
|
326
326
|
}
|
|
327
327
|
];
|
|
328
328
|
|
|
329
|
+
// src/photo.ts
|
|
330
|
+
var PHOTO_TYPE = "photo";
|
|
331
|
+
var PHOTO_FORMATS = {
|
|
332
|
+
jpeg: ["image/jpeg"],
|
|
333
|
+
png: ["image/png"],
|
|
334
|
+
heic: ["image/heic", "image/heif"],
|
|
335
|
+
webp: ["image/webp"]
|
|
336
|
+
};
|
|
337
|
+
var FORMAT_NAMES = { jpeg: "JPEG", png: "PNG", heic: "HEIC", webp: "WebP" };
|
|
338
|
+
var ALL_FORMATS = Object.keys(PHOTO_FORMATS);
|
|
339
|
+
var DEFAULT_PHOTO_FILES = 3;
|
|
340
|
+
var MAX_PHOTO_FILES = 5;
|
|
341
|
+
var DEFAULT_PHOTO_SIZE_MB = 10;
|
|
342
|
+
var MAX_PHOTO_SIZE_MB = 25;
|
|
343
|
+
var MAX_PHOTO_ID_LENGTH = 200;
|
|
344
|
+
var MAX_PHOTO_NAME_LENGTH = 255;
|
|
345
|
+
var wholeIn = (v, min, max) => typeof v === "number" && Number.isInteger(v) && v >= min && v <= max;
|
|
346
|
+
function photoLimits(field) {
|
|
347
|
+
const maxFiles = wholeIn(field.props?.maxFiles, 1, MAX_PHOTO_FILES) ? field.props.maxFiles : DEFAULT_PHOTO_FILES;
|
|
348
|
+
const maxSizeMb = wholeIn(field.props?.maxSizeMb, 1, MAX_PHOTO_SIZE_MB) ? field.props.maxSizeMb : DEFAULT_PHOTO_SIZE_MB;
|
|
349
|
+
const raw = field.props?.formats;
|
|
350
|
+
const picked = Array.isArray(raw) ? ALL_FORMATS.filter((f) => raw.includes(f)) : [];
|
|
351
|
+
const formats = picked.length ? picked : [...ALL_FORMATS];
|
|
352
|
+
return {
|
|
353
|
+
maxFiles,
|
|
354
|
+
maxSizeMb,
|
|
355
|
+
maxBytes: maxSizeMb * 1024 * 1024,
|
|
356
|
+
formats,
|
|
357
|
+
mimeTypes: formats.flatMap((f) => PHOTO_FORMATS[f])
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function isPhotoRef(v) {
|
|
361
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return false;
|
|
362
|
+
const r = v;
|
|
363
|
+
return typeof r.id === "string" && r.id.length > 0 && r.id.length <= MAX_PHOTO_ID_LENGTH && typeof r.name === "string" && r.name.length <= MAX_PHOTO_NAME_LENGTH && typeof r.type === "string" && typeof r.size === "number" && Number.isInteger(r.size) && r.size > 0;
|
|
364
|
+
}
|
|
365
|
+
function photoRefs(value) {
|
|
366
|
+
if (!Array.isArray(value)) return [];
|
|
367
|
+
const refs = [];
|
|
368
|
+
for (const r of value) if (isPhotoRef(r)) refs.push({ id: r.id, name: r.name, type: r.type, size: r.size });
|
|
369
|
+
return refs;
|
|
370
|
+
}
|
|
371
|
+
var plural2 = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
372
|
+
function photoFormatList(formats) {
|
|
373
|
+
const names = formats.map((f) => FORMAT_NAMES[f]);
|
|
374
|
+
return names.length > 1 ? `${names.slice(0, -1).join(", ")} or ${names[names.length - 1]}` : names[0] ?? "";
|
|
375
|
+
}
|
|
376
|
+
var photoTypeMessage = (field) => `Use a ${photoFormatList(photoLimits(field).formats)} photo.`;
|
|
377
|
+
var photoSizeMessage = (field) => `Each photo must be ${photoLimits(field).maxSizeMb} MB or smaller.`;
|
|
378
|
+
var photoCountMessage = (field) => `Add at most ${plural2(photoLimits(field).maxFiles, "photo")}.`;
|
|
379
|
+
var photoFieldType = {
|
|
380
|
+
type: PHOTO_TYPE,
|
|
381
|
+
label: "Photo",
|
|
382
|
+
category: "media",
|
|
383
|
+
value: { json: true },
|
|
384
|
+
operators: ["answered", "empty"],
|
|
385
|
+
a11y: { semantics: "group" },
|
|
386
|
+
isEmpty: (value) => value === void 0 || value === null || Array.isArray(value) && value.length === 0,
|
|
387
|
+
validate: (value, field) => {
|
|
388
|
+
if (!Array.isArray(value) || !value.every(isPhotoRef)) return "Add a photo.";
|
|
389
|
+
const limits = photoLimits(field);
|
|
390
|
+
if (value.length > limits.maxFiles) return photoCountMessage(field);
|
|
391
|
+
const refs = value;
|
|
392
|
+
if (new Set(refs.map((r) => r.id)).size !== refs.length) return "Each photo can be added once.";
|
|
393
|
+
if (refs.some((r) => !limits.mimeTypes.includes(r.type.toLowerCase()))) return photoTypeMessage(field);
|
|
394
|
+
if (refs.some((r) => r.size > limits.maxBytes)) return photoSizeMessage(field);
|
|
395
|
+
return null;
|
|
396
|
+
},
|
|
397
|
+
normalize: (value) => Array.isArray(value) && value.every(isPhotoRef) ? photoRefs(value) : value,
|
|
398
|
+
summarize: (value) => photoRefs(value).map((r) => r.name || "Photo").join(", "),
|
|
399
|
+
samples: () => [[{ id: "sample-photo", name: "photo.jpg", type: "image/jpeg", size: 1024 }]]
|
|
400
|
+
};
|
|
401
|
+
|
|
329
402
|
// src/fields.ts
|
|
330
403
|
var REGISTRY = /* @__PURE__ */ Symbol.for("@marktiderman/genesis-forms/field-types");
|
|
331
404
|
var globalScope = globalThis;
|
|
@@ -361,12 +434,78 @@ function summarizeValue(value, field) {
|
|
|
361
434
|
if (Array.isArray(value)) return value.join(", ");
|
|
362
435
|
return String(value);
|
|
363
436
|
}
|
|
437
|
+
var OPTION_LIMITS = {
|
|
438
|
+
/** Characters in a description. */
|
|
439
|
+
description: 500,
|
|
440
|
+
/** Characters in an image URL. */
|
|
441
|
+
image: 2048,
|
|
442
|
+
/** Attributes on one option. */
|
|
443
|
+
attributes: 20,
|
|
444
|
+
/** Characters in an attribute's name. */
|
|
445
|
+
attributeName: 64,
|
|
446
|
+
/** Characters in one attribute value. */
|
|
447
|
+
attributeValue: 200,
|
|
448
|
+
/** Values in one attribute's list. */
|
|
449
|
+
attributeItems: 50
|
|
450
|
+
};
|
|
451
|
+
function isHttpsUrl(url) {
|
|
452
|
+
return typeof url === "string" && url.length <= OPTION_LIMITS.image && /^https:\/\/[^\s/?#@\\]+(?:[/?#][^\s]*)?$/i.test(url) && // No control characters anywhere (a line break would end a CSV row or an HTML attribute).
|
|
453
|
+
![...url].some((c) => c.charCodeAt(0) < 32 || c.charCodeAt(0) === 127);
|
|
454
|
+
}
|
|
455
|
+
function attributeName(name) {
|
|
456
|
+
return name.trim() !== "" && name.length <= OPTION_LIMITS.attributeName && !(name in Object.prototype);
|
|
457
|
+
}
|
|
458
|
+
function readAttributes(raw) {
|
|
459
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
460
|
+
const out = {};
|
|
461
|
+
let count = 0;
|
|
462
|
+
for (const name of Object.keys(raw)) {
|
|
463
|
+
if (count >= OPTION_LIMITS.attributes) break;
|
|
464
|
+
if (!attributeName(name)) continue;
|
|
465
|
+
const v = raw[name];
|
|
466
|
+
const fits = (x) => typeof x === "string" && x.length <= OPTION_LIMITS.attributeValue;
|
|
467
|
+
if (fits(v)) out[name] = v;
|
|
468
|
+
else if (Array.isArray(v) && v.length <= OPTION_LIMITS.attributeItems && v.every(fits)) out[name] = [...v];
|
|
469
|
+
else continue;
|
|
470
|
+
count++;
|
|
471
|
+
}
|
|
472
|
+
return count ? out : void 0;
|
|
473
|
+
}
|
|
474
|
+
function readOption(raw) {
|
|
475
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
476
|
+
const o = raw;
|
|
477
|
+
if (typeof o.value !== "string") return null;
|
|
478
|
+
const out = { value: o.value };
|
|
479
|
+
if (typeof o.label === "string") out.label = o.label;
|
|
480
|
+
if (typeof o.description === "string" && o.description.trim() && o.description.length <= OPTION_LIMITS.description) {
|
|
481
|
+
out.description = o.description;
|
|
482
|
+
}
|
|
483
|
+
if (isHttpsUrl(o.image)) out.image = o.image;
|
|
484
|
+
const attributes = readAttributes(o.attributes);
|
|
485
|
+
if (attributes) out.attributes = attributes;
|
|
486
|
+
return out;
|
|
487
|
+
}
|
|
488
|
+
var OTHER_MAX_LENGTH = 200;
|
|
489
|
+
function otherOption(field) {
|
|
490
|
+
const other = field.props?.other;
|
|
491
|
+
if (other === true) return { label: "Other" };
|
|
492
|
+
if (other && typeof other === "object" && !Array.isArray(other)) {
|
|
493
|
+
const label = typeof other.label === "string" && other.label.trim() ? other.label.trim() : "Other";
|
|
494
|
+
return { label };
|
|
495
|
+
}
|
|
496
|
+
return null;
|
|
497
|
+
}
|
|
498
|
+
function isOtherValue(field, value) {
|
|
499
|
+
return otherOption(field) !== null && typeof value === "string" && !choiceOptions(field).some((o) => o.value === value);
|
|
500
|
+
}
|
|
501
|
+
var typedOther = (field, value) => isOtherValue(field, value) && value.trim() !== "" && value.length <= OTHER_MAX_LENGTH;
|
|
364
502
|
function choiceOptions(field) {
|
|
365
503
|
const raw = field.props?.options;
|
|
366
504
|
if (!Array.isArray(raw)) return [];
|
|
367
|
-
return raw.flatMap(
|
|
368
|
-
|
|
369
|
-
|
|
505
|
+
return raw.flatMap((o) => {
|
|
506
|
+
const option = readOption(o);
|
|
507
|
+
return option ? [option] : [];
|
|
508
|
+
});
|
|
370
509
|
}
|
|
371
510
|
function optionLabel(field, value) {
|
|
372
511
|
const option = choiceOptions(field).find((o) => o.value === value);
|
|
@@ -486,7 +625,7 @@ var builtins = [
|
|
|
486
625
|
value: { json: true },
|
|
487
626
|
operators: ["eq", "neq", "in", "answered", "empty"],
|
|
488
627
|
a11y: { semantics: "group" },
|
|
489
|
-
validate: (value, field) => typeof value === "string" && choiceOptions(field).some((o) => o.value === value) ? null : "Choose one of the options.",
|
|
628
|
+
validate: (value, field) => typeof value === "string" && (choiceOptions(field).some((o) => o.value === value) || typedOther(field, value)) ? null : "Choose one of the options.",
|
|
490
629
|
summarize: (value, field) => optionLabel(field, String(value))
|
|
491
630
|
},
|
|
492
631
|
{
|
|
@@ -498,7 +637,8 @@ var builtins = [
|
|
|
498
637
|
a11y: { semantics: "group" },
|
|
499
638
|
validate: (value, field) => {
|
|
500
639
|
const allowed = new Set(choiceOptions(field).map((o) => o.value));
|
|
501
|
-
|
|
640
|
+
const others = Array.isArray(value) ? value.filter((v) => typeof v === "string" && !allowed.has(v)) : [];
|
|
641
|
+
return Array.isArray(value) && new Set(value).size === value.length && value.every((v) => typeof v === "string") && (others.length === 0 || others.length === 1 && typedOther(field, others[0])) ? null : "Choose from the options.";
|
|
502
642
|
},
|
|
503
643
|
summarize: (value, field) => Array.isArray(value) ? value.map((v) => optionLabel(field, String(v))).join(", ") : String(value)
|
|
504
644
|
},
|
|
@@ -519,7 +659,7 @@ var builtins = [
|
|
|
519
659
|
value: { json: true },
|
|
520
660
|
operators: ["eq", "neq", "in", "answered", "empty"],
|
|
521
661
|
a11y: { semantics: "control" },
|
|
522
|
-
validate: (value, field) => typeof value === "string" && choiceOptions(field).some((o) => o.value === value) ? null : "Choose one of the options.",
|
|
662
|
+
validate: (value, field) => typeof value === "string" && (choiceOptions(field).some((o) => o.value === value) || typedOther(field, value)) ? null : "Choose one of the options.",
|
|
523
663
|
summarize: (value, field) => optionLabel(field, String(value))
|
|
524
664
|
},
|
|
525
665
|
{
|
|
@@ -626,7 +766,7 @@ function dateLimits(field) {
|
|
|
626
766
|
if (typeof max === "string" && isIsoDate(max)) out.max = max;
|
|
627
767
|
return out;
|
|
628
768
|
}
|
|
629
|
-
for (const manifest of [...builtins, ...structuredFieldTypes, calculatedFieldType]) registerFieldType(manifest);
|
|
769
|
+
for (const manifest of [...builtins, ...structuredFieldTypes, calculatedFieldType, photoFieldType]) registerFieldType(manifest);
|
|
630
770
|
|
|
631
771
|
// src/logic.ts
|
|
632
772
|
function own(record, key) {
|