@bettercms-ai/types 1.40.0 → 1.41.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/dist/index.js CHANGED
@@ -427,7 +427,27 @@ var COMPONENT_PROP_TYPES = [
427
427
  "color",
428
428
  "file",
429
429
  "icon",
430
- "multiselect"
430
+ "multiselect",
431
+ // V2 schema language (F1.x). `object` is ONE nested value of named `config.fields`, stored as
432
+ // `{ [childKey]: value }` like the model field `object`; the six after it store what the
433
+ // same-named content-model FIELD stores (`location` is the V2 geopoint) and are judged by
434
+ // that field's validator.
435
+ "object",
436
+ "datetime",
437
+ "slug",
438
+ "email",
439
+ "phone",
440
+ "json",
441
+ "location",
442
+ // V2 leaves, each storing what the same-named content-model FIELD stores: a taxonomy string[],
443
+ // a rating number, a code value (`{ language?, code }` or a string).
444
+ "tags",
445
+ "rating",
446
+ "code",
447
+ // CMS items, as the same-named content-model FIELDS store them: an entry id (or a polymorphic
448
+ // `{ entryId, modelSlug }`), and a list of them. Delivery hydrates them like an entry's.
449
+ "reference",
450
+ "multi-reference"
431
451
  ];
432
452
  var SLOT_MAX_ITEMS = 12;
433
453
  var SLOT_ITEMS_CAP = 50;
@@ -2688,7 +2708,7 @@ function listItems(f) {
2688
2708
  }
2689
2709
  function zoneFields(f) {
2690
2710
  const c = f.config ?? {};
2691
- if (f.type === "group") return c.fields ?? f.fields ?? [];
2711
+ if (f.type === "group" || f.type === "object") return c.fields ?? f.fields ?? [];
2692
2712
  if (f.type === "array" && c.zones?.nonRepeatable) return c.zones.nonRepeatable;
2693
2713
  return void 0;
2694
2714
  }
@@ -2927,12 +2947,101 @@ function buttonLinkPairs(candidates) {
2927
2947
 
2928
2948
  // src/button-look.ts
2929
2949
  import { ICON_MARKUP, iconSvg } from "@bettercms-ai/icons/markup";
2950
+
2951
+ // src/link-value.ts
2952
+ var LINK_RELS = ["nofollow", "sponsored", "ugc"];
2953
+ var LINK_VALUE_TYPES = ["url", "page", "entry", "email", "phone", "anchor", "section", "file"];
2954
+ var isPlainObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
2955
+ function isLinkObject(value) {
2956
+ return isPlainObject(value) && typeof value.href === "string";
2957
+ }
2958
+ function linkHref(value) {
2959
+ if (typeof value === "string") return value;
2960
+ return isLinkObject(value) ? value.href : null;
2961
+ }
2962
+ function linkRefOf(value) {
2963
+ if (!isLinkObject(value) || value.type !== "page" && value.type !== "entry") return null;
2964
+ const id = value.ref ?? (value.type === "page" ? value.pageId : value.entryId);
2965
+ return typeof id === "string" && id.length > 0 ? { kind: value.type, id } : null;
2966
+ }
2967
+ var isScriptHref = (href) => /^(javascript|data|vbscript):/i.test(href.replace(/[\u0000- \u007F]/g, ""));
2968
+ var newTabOf = (v) => v.newTab === true || v.newTab === void 0 && v.target === "_blank";
2969
+ function linkValueProblem(value) {
2970
+ if (!isPlainObject(value)) return "link_value_invalid: must be an href string or a link object { type, href, \u2026 }";
2971
+ const v = value;
2972
+ if (typeof v.href !== "string") return "link_value_invalid: needs an href string";
2973
+ if (isScriptHref(v.href)) return "link_value_invalid: script links (javascript:, data:, vbscript:) aren't allowed";
2974
+ if (v.type !== void 0 && !LINK_VALUE_TYPES.includes(v.type)) {
2975
+ return `link_value_invalid: unknown type "${String(v.type)}" (${LINK_VALUE_TYPES.join(", ")})`;
2976
+ }
2977
+ for (const key of ["title", "section", "ref"]) {
2978
+ if (v[key] !== void 0 && typeof v[key] !== "string") return `link_value_invalid: ${key} must be a string`;
2979
+ }
2980
+ if (v.ref !== void 0 && v.type !== "page" && v.type !== "entry") return "link_value_invalid: ref is the target of a page or entry link";
2981
+ if (v.newTab !== void 0 && typeof v.newTab !== "boolean") return "link_value_invalid: newTab must be true or false";
2982
+ if (v.target !== void 0 && v.target !== "_self" && v.target !== "_blank") return 'link_value_invalid: target must be "_self" or "_blank"';
2983
+ if (typeof v.newTab === "boolean" && v.target !== void 0 && v.newTab !== (v.target === "_blank")) {
2984
+ return "link_value_invalid: newTab and target disagree";
2985
+ }
2986
+ if (v.rel !== void 0 && !LINK_RELS.includes(v.rel)) {
2987
+ return `link_value_invalid: unknown rel "${String(v.rel)}" (nofollow, sponsored or ugc)`;
2988
+ }
2989
+ return null;
2990
+ }
2991
+ function normalizeLinkValue(value) {
2992
+ if (!isLinkObject(value)) return value;
2993
+ const out = { ...value };
2994
+ delete out.resolved;
2995
+ if (out.rel === "follow") delete out.rel;
2996
+ if (out.newTab === void 0 && out.target !== void 0) out.newTab = out.target === "_blank";
2997
+ const ref = linkRefOf(out);
2998
+ if (ref && out.ref === void 0) out.ref = ref.id;
2999
+ return out;
3000
+ }
3001
+ function linkAnchorOf(value) {
3002
+ if (typeof value === "string") return { href: value, newTab: false };
3003
+ if (!isLinkObject(value)) return null;
3004
+ return {
3005
+ href: value.href,
3006
+ newTab: newTabOf(value),
3007
+ ...LINK_RELS.includes(value.rel) ? { rel: value.rel } : {}
3008
+ };
3009
+ }
3010
+
3011
+ // src/button-look.ts
2930
3012
  var BUTTON_VARIANTS = ["primary", "secondary", "tertiary", "brand", "dark", "light", "outline", "link"];
2931
3013
  var BUTTON_ICONS = ICON_MARKUP;
2932
3014
  var BUTTON_ICON_NAMES = DOCK_ICON_NAMES;
2933
3015
  function buttonVariantOf(value) {
2934
3016
  return BUTTON_VARIANTS.includes(value) ? value : "primary";
2935
3017
  }
3018
+ var SITE_CLASS = /^[^\s"'<>`=]{1,64}$/;
3019
+ var LOOK_VALUE = /^[a-z0-9][a-z0-9-]{0,63}$/;
3020
+ function buttonSiteLookOf(value) {
3021
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3022
+ const v = value;
3023
+ const tokens = (list) => Array.isArray(list) ? list.filter((c) => typeof c === "string" && SITE_CLASS.test(c)) : [];
3024
+ const variant = v.attrs?.["data-variant"];
3025
+ const classes = tokens(v.classes);
3026
+ const also = tokens(v.also);
3027
+ const size = tokens(v.size);
3028
+ const sizeAlso = tokens(v.sizeAlso);
3029
+ const attrs = typeof variant === "string" && LOOK_VALUE.test(variant) ? { "data-variant": variant } : void 0;
3030
+ if (classes.length === 0 && !attrs) return null;
3031
+ return {
3032
+ classes,
3033
+ ...attrs ? { attrs } : {},
3034
+ ...also.length ? { also } : {},
3035
+ ...size.length ? { size } : {},
3036
+ ...sizeAlso.length ? { sizeAlso } : {}
3037
+ };
3038
+ }
3039
+ function buttonLookValue(slug, siteLook) {
3040
+ return siteLook?.attrs?.["data-variant"] ?? slug;
3041
+ }
3042
+ function isButtonLookValue(value) {
3043
+ return typeof value === "string" && LOOK_VALUE.test(value);
3044
+ }
2936
3045
  function buttonIconOf(value) {
2937
3046
  return typeof value === "string" && DOCK_ICON_NAME_SET.has(value) ? value : null;
2938
3047
  }
@@ -2940,14 +3049,15 @@ function buttonIconSvg(name, cls) {
2940
3049
  return iconSvg(name, cls);
2941
3050
  }
2942
3051
  var BUTTON_SIZES = ["sm", "md", "lg"];
2943
- var LINK_RELS = ["nofollow", "sponsored", "ugc"];
2944
3052
  function anchorTargetRel(newTab, rel) {
2945
3053
  const tokens = [...newTab ? ["noopener", "noreferrer"] : [], ...LINK_RELS.includes(rel) ? [rel] : []];
2946
3054
  return `${newTab ? ' target="_blank"' : ""}${tokens.length ? ` rel="${tokens.join(" ")}"` : ""}`;
2947
3055
  }
2948
- var hrefOf = (href) => typeof href === "string" ? href : href && typeof href === "object" && typeof href.href === "string" ? href.href : null;
3056
+ var hrefOf = linkHref;
2949
3057
  function readButtonValue(value) {
2950
3058
  const v = value && typeof value === "object" ? value : {};
3059
+ const link = isLinkObject(v.href) ? linkAnchorOf(v.href) : null;
3060
+ const rel = v.rel ?? link?.rel;
2951
3061
  return {
2952
3062
  label: typeof v.label === "string" ? v.label : "",
2953
3063
  href: hrefOf(v.href) ?? "",
@@ -2955,16 +3065,22 @@ function readButtonValue(value) {
2955
3065
  size: BUTTON_SIZES.includes(v.size) ? v.size : "md",
2956
3066
  arrow: v.arrow === true,
2957
3067
  icon: buttonIconOf(v.icon),
2958
- newTab: v.newTab === true,
2959
- ...LINK_RELS.includes(v.rel) ? { rel: v.rel } : {}
3068
+ newTab: v.newTab === void 0 ? link?.newTab === true : v.newTab === true,
3069
+ ...LINK_RELS.includes(rel) ? { rel } : {}
2960
3070
  };
2961
3071
  }
2962
- function buttonValueProblem(value) {
3072
+ function buttonValueProblem(value, looks) {
2963
3073
  if (!value || typeof value !== "object" || Array.isArray(value)) return "must be a { label, href } button";
2964
3074
  const v = value;
2965
3075
  if (typeof v.label !== "string") return "needs a label";
2966
3076
  if (hrefOf(v.href) === null) return "needs an href";
2967
- if (v.variant !== void 0 && !BUTTON_VARIANTS.includes(v.variant)) return `has an unknown variant "${String(v.variant)}"`;
3077
+ if (isLinkObject(v.href)) {
3078
+ const problem = linkValueProblem(v.href);
3079
+ if (problem) return `href ${problem}`;
3080
+ }
3081
+ if (v.variant !== void 0 && !BUTTON_VARIANTS.includes(v.variant) && !(typeof v.variant === "string" && looks?.has(v.variant))) {
3082
+ return `has an unknown variant "${String(v.variant)}"${looks?.size ? ` (this project's looks: ${[...looks].join(", ")})` : ""}`;
3083
+ }
2968
3084
  if (v.size !== void 0 && !BUTTON_SIZES.includes(v.size)) return `has an unknown size "${String(v.size)}"`;
2969
3085
  if (v.arrow !== void 0 && typeof v.arrow !== "boolean") return "arrow must be true or false";
2970
3086
  if (v.newTab !== void 0 && typeof v.newTab !== "boolean") return "newTab must be true or false";
@@ -2979,7 +3095,14 @@ function buttonBlockProps(value) {
2979
3095
  const out = {};
2980
3096
  if (typeof v.label === "string") out.text = v.label;
2981
3097
  const href = hrefOf(v.href);
2982
- if (href !== null) out.href = href;
3098
+ const script = href !== null && isScriptHref(href);
3099
+ if (href !== null) out.href = script ? "#" : href;
3100
+ if (isLinkObject(v.href) && !script) {
3101
+ const link = linkAnchorOf(v.href);
3102
+ out.newTab = link.newTab;
3103
+ out.rel = link.rel;
3104
+ }
3105
+ if (v.rel !== void 0) out.rel = v.rel;
2983
3106
  for (const key of BUTTON_LOOK_KEYS) if (key in v) out[key] = v[key];
2984
3107
  return out;
2985
3108
  }
@@ -3032,6 +3155,9 @@ function buttonValueOfInstance(def, overrides) {
3032
3155
  return readButtonValue({ ...props, label: props.text });
3033
3156
  }
3034
3157
 
3158
+ // src/content-model.ts
3159
+ var OPTION_DESCRIPTION_MAX = 140;
3160
+
3035
3161
  // src/locator.ts
3036
3162
  var LANDMARK_SELECTOR = "header,nav,footer";
3037
3163
  var LANDMARK_TAGS = /* @__PURE__ */ new Set(["HEADER", "NAV", "FOOTER"]);
@@ -3113,8 +3239,10 @@ export {
3113
3239
  LAYOUT_SECTION_ICONS,
3114
3240
  LAYOUT_SECTION_ICON_SET,
3115
3241
  LINK_RELS,
3242
+ LINK_VALUE_TYPES,
3116
3243
  MAIN_SELECTOR,
3117
3244
  NAVIGATION_SECTION_ID,
3245
+ OPTION_DESCRIPTION_MAX,
3118
3246
  PAGE_CONTENT_ANCHOR_ID,
3119
3247
  RESPONSIVE_CSS,
3120
3248
  SECTION_DOCTRINE,
@@ -3131,6 +3259,8 @@ export {
3131
3259
  buttonIconOf,
3132
3260
  buttonIconSvg,
3133
3261
  buttonLinkPairs,
3262
+ buttonLookValue,
3263
+ buttonSiteLookOf,
3134
3264
  buttonValueOfInstance,
3135
3265
  buttonValueProblem,
3136
3266
  buttonVariantOf,
@@ -3149,10 +3279,18 @@ export {
3149
3279
  hideOnAttr,
3150
3280
  inWindow,
3151
3281
  isBlock,
3282
+ isButtonLookValue,
3152
3283
  isChromeLandmark,
3284
+ isLinkObject,
3285
+ isScriptHref,
3153
3286
  keyWords,
3154
3287
  landmarkRegions,
3288
+ linkAnchorOf,
3289
+ linkHref,
3290
+ linkRefOf,
3291
+ linkValueProblem,
3155
3292
  locatorFor,
3293
+ normalizeLinkValue,
3156
3294
  normalizeWindow,
3157
3295
  parsePropsAttribute,
3158
3296
  pickFieldStyle,