@marktiderman/genesis-forms 0.4.1 → 0.5.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.
@@ -1,4 +1,4 @@
1
- import { J as Json, F as FormDefinition, a as FieldBlock } from './types-BBKBQ8sp.cjs';
1
+ import { J as Json, F as FormDefinition, a as FieldBlock } from './types-BhYUbLJ-.cjs';
2
2
 
3
3
  /**
4
4
  * The contract every destination keeps. A destination sends one checked
@@ -1,4 +1,4 @@
1
- import { J as Json, F as FormDefinition, a as FieldBlock } from './types-BBKBQ8sp.js';
1
+ import { J as Json, F as FormDefinition, a as FieldBlock } from './types-BhYUbLJ-.js';
2
2
 
3
3
  /**
4
4
  * The contract every destination keeps. A destination sends one checked
@@ -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,6 +434,20 @@ function summarizeValue(value, field) {
361
434
  if (Array.isArray(value)) return value.join(", ");
362
435
  return String(value);
363
436
  }
437
+ var OTHER_MAX_LENGTH = 200;
438
+ function otherOption(field) {
439
+ const other = field.props?.other;
440
+ if (other === true) return { label: "Other" };
441
+ if (other && typeof other === "object" && !Array.isArray(other)) {
442
+ const label = typeof other.label === "string" && other.label.trim() ? other.label.trim() : "Other";
443
+ return { label };
444
+ }
445
+ return null;
446
+ }
447
+ function isOtherValue(field, value) {
448
+ return otherOption(field) !== null && typeof value === "string" && !choiceOptions(field).some((o) => o.value === value);
449
+ }
450
+ var typedOther = (field, value) => isOtherValue(field, value) && value.trim() !== "" && value.length <= OTHER_MAX_LENGTH;
364
451
  function choiceOptions(field) {
365
452
  const raw = field.props?.options;
366
453
  if (!Array.isArray(raw)) return [];
@@ -486,7 +573,7 @@ var builtins = [
486
573
  value: { json: true },
487
574
  operators: ["eq", "neq", "in", "answered", "empty"],
488
575
  a11y: { semantics: "group" },
489
- validate: (value, field) => typeof value === "string" && choiceOptions(field).some((o) => o.value === value) ? null : "Choose one of the options.",
576
+ validate: (value, field) => typeof value === "string" && (choiceOptions(field).some((o) => o.value === value) || typedOther(field, value)) ? null : "Choose one of the options.",
490
577
  summarize: (value, field) => optionLabel(field, String(value))
491
578
  },
492
579
  {
@@ -498,7 +585,8 @@ var builtins = [
498
585
  a11y: { semantics: "group" },
499
586
  validate: (value, field) => {
500
587
  const allowed = new Set(choiceOptions(field).map((o) => o.value));
501
- return Array.isArray(value) && value.length <= allowed.size && new Set(value).size === value.length && value.every((v) => typeof v === "string" && allowed.has(v)) ? null : "Choose from the options.";
588
+ const others = Array.isArray(value) ? value.filter((v) => typeof v === "string" && !allowed.has(v)) : [];
589
+ 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
590
  },
503
591
  summarize: (value, field) => Array.isArray(value) ? value.map((v) => optionLabel(field, String(v))).join(", ") : String(value)
504
592
  },
@@ -519,7 +607,7 @@ var builtins = [
519
607
  value: { json: true },
520
608
  operators: ["eq", "neq", "in", "answered", "empty"],
521
609
  a11y: { semantics: "control" },
522
- validate: (value, field) => typeof value === "string" && choiceOptions(field).some((o) => o.value === value) ? null : "Choose one of the options.",
610
+ validate: (value, field) => typeof value === "string" && (choiceOptions(field).some((o) => o.value === value) || typedOther(field, value)) ? null : "Choose one of the options.",
523
611
  summarize: (value, field) => optionLabel(field, String(value))
524
612
  },
525
613
  {
@@ -626,7 +714,7 @@ function dateLimits(field) {
626
714
  if (typeof max === "string" && isIsoDate(max)) out.max = max;
627
715
  return out;
628
716
  }
629
- for (const manifest of [...builtins, ...structuredFieldTypes, calculatedFieldType]) registerFieldType(manifest);
717
+ for (const manifest of [...builtins, ...structuredFieldTypes, calculatedFieldType, photoFieldType]) registerFieldType(manifest);
630
718
 
631
719
  // src/logic.ts
632
720
  function own(record, key) {