@bettercms-ai/types 1.39.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
@@ -377,7 +377,8 @@ function splitButtonStyle(style) {
377
377
  paint: Object.keys(paint).length ? paint : void 0
378
378
  };
379
379
  }
380
- var FIELD_STYLE_KEYS = ["contentWidth", "contentAlign", "marginTop", "marginBottom", "align", "fontSize", "weight"];
380
+ var FIELD_STYLE_KEYS = ["contentWidth", "contentAlign", "marginTop", "marginBottom", "align", "fontSize", "weight", "size"];
381
+ var FIELD_SIZE_ZOOM = { sm: "0.875", lg: "1.125" };
381
382
  var FIELD_KEY_SET = new Set(FIELD_STYLE_KEYS);
382
383
  function pickFieldStyle(style) {
383
384
  const out = {};
@@ -389,6 +390,8 @@ function fieldStyleToCss(style) {
389
390
  const picked = pickFieldStyle(style);
390
391
  const css = { ...blockStyleToCss(picked) };
391
392
  Object.assign(css, blockStyleToColumnCss(picked));
393
+ const zoom = own(FIELD_SIZE_ZOOM, picked.size);
394
+ if (zoom) css.zoom = zoom;
392
395
  return Object.keys(css).length ? css : void 0;
393
396
  }
394
397
  function blockStyleToColumnCss(style) {
@@ -424,7 +427,27 @@ var COMPONENT_PROP_TYPES = [
424
427
  "color",
425
428
  "file",
426
429
  "icon",
427
- "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"
428
451
  ];
429
452
  var SLOT_MAX_ITEMS = 12;
430
453
  var SLOT_ITEMS_CAP = 50;
@@ -2685,7 +2708,7 @@ function listItems(f) {
2685
2708
  }
2686
2709
  function zoneFields(f) {
2687
2710
  const c = f.config ?? {};
2688
- if (f.type === "group") return c.fields ?? f.fields ?? [];
2711
+ if (f.type === "group" || f.type === "object") return c.fields ?? f.fields ?? [];
2689
2712
  if (f.type === "array" && c.zones?.nonRepeatable) return c.zones.nonRepeatable;
2690
2713
  return void 0;
2691
2714
  }
@@ -2924,12 +2947,101 @@ function buttonLinkPairs(candidates) {
2924
2947
 
2925
2948
  // src/button-look.ts
2926
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
2927
3012
  var BUTTON_VARIANTS = ["primary", "secondary", "tertiary", "brand", "dark", "light", "outline", "link"];
2928
3013
  var BUTTON_ICONS = ICON_MARKUP;
2929
3014
  var BUTTON_ICON_NAMES = DOCK_ICON_NAMES;
2930
3015
  function buttonVariantOf(value) {
2931
3016
  return BUTTON_VARIANTS.includes(value) ? value : "primary";
2932
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
+ }
2933
3045
  function buttonIconOf(value) {
2934
3046
  return typeof value === "string" && DOCK_ICON_NAME_SET.has(value) ? value : null;
2935
3047
  }
@@ -2937,14 +3049,15 @@ function buttonIconSvg(name, cls) {
2937
3049
  return iconSvg(name, cls);
2938
3050
  }
2939
3051
  var BUTTON_SIZES = ["sm", "md", "lg"];
2940
- var LINK_RELS = ["nofollow", "sponsored", "ugc"];
2941
3052
  function anchorTargetRel(newTab, rel) {
2942
3053
  const tokens = [...newTab ? ["noopener", "noreferrer"] : [], ...LINK_RELS.includes(rel) ? [rel] : []];
2943
3054
  return `${newTab ? ' target="_blank"' : ""}${tokens.length ? ` rel="${tokens.join(" ")}"` : ""}`;
2944
3055
  }
2945
- var hrefOf = (href) => typeof href === "string" ? href : href && typeof href === "object" && typeof href.href === "string" ? href.href : null;
3056
+ var hrefOf = linkHref;
2946
3057
  function readButtonValue(value) {
2947
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;
2948
3061
  return {
2949
3062
  label: typeof v.label === "string" ? v.label : "",
2950
3063
  href: hrefOf(v.href) ?? "",
@@ -2952,16 +3065,22 @@ function readButtonValue(value) {
2952
3065
  size: BUTTON_SIZES.includes(v.size) ? v.size : "md",
2953
3066
  arrow: v.arrow === true,
2954
3067
  icon: buttonIconOf(v.icon),
2955
- newTab: v.newTab === true,
2956
- ...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 } : {}
2957
3070
  };
2958
3071
  }
2959
- function buttonValueProblem(value) {
3072
+ function buttonValueProblem(value, looks) {
2960
3073
  if (!value || typeof value !== "object" || Array.isArray(value)) return "must be a { label, href } button";
2961
3074
  const v = value;
2962
3075
  if (typeof v.label !== "string") return "needs a label";
2963
3076
  if (hrefOf(v.href) === null) return "needs an href";
2964
- 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
+ }
2965
3084
  if (v.size !== void 0 && !BUTTON_SIZES.includes(v.size)) return `has an unknown size "${String(v.size)}"`;
2966
3085
  if (v.arrow !== void 0 && typeof v.arrow !== "boolean") return "arrow must be true or false";
2967
3086
  if (v.newTab !== void 0 && typeof v.newTab !== "boolean") return "newTab must be true or false";
@@ -2976,7 +3095,14 @@ function buttonBlockProps(value) {
2976
3095
  const out = {};
2977
3096
  if (typeof v.label === "string") out.text = v.label;
2978
3097
  const href = hrefOf(v.href);
2979
- 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;
2980
3106
  for (const key of BUTTON_LOOK_KEYS) if (key in v) out[key] = v[key];
2981
3107
  return out;
2982
3108
  }
@@ -3029,6 +3155,9 @@ function buttonValueOfInstance(def, overrides) {
3029
3155
  return readButtonValue({ ...props, label: props.text });
3030
3156
  }
3031
3157
 
3158
+ // src/content-model.ts
3159
+ var OPTION_DESCRIPTION_MAX = 140;
3160
+
3032
3161
  // src/locator.ts
3033
3162
  var LANDMARK_SELECTOR = "header,nav,footer";
3034
3163
  var LANDMARK_TAGS = /* @__PURE__ */ new Set(["HEADER", "NAV", "FOOTER"]);
@@ -3103,14 +3232,17 @@ export {
3103
3232
  DOCK_BUDGET,
3104
3233
  DOCK_ICON_NAMES,
3105
3234
  DOCK_ICON_NAME_SET,
3235
+ FIELD_SIZE_ZOOM,
3106
3236
  FIELD_STYLE_KEYS,
3107
3237
  FOOTER_SECTION_ID,
3108
3238
  LANDMARK_SELECTOR,
3109
3239
  LAYOUT_SECTION_ICONS,
3110
3240
  LAYOUT_SECTION_ICON_SET,
3111
3241
  LINK_RELS,
3242
+ LINK_VALUE_TYPES,
3112
3243
  MAIN_SELECTOR,
3113
3244
  NAVIGATION_SECTION_ID,
3245
+ OPTION_DESCRIPTION_MAX,
3114
3246
  PAGE_CONTENT_ANCHOR_ID,
3115
3247
  RESPONSIVE_CSS,
3116
3248
  SECTION_DOCTRINE,
@@ -3127,6 +3259,8 @@ export {
3127
3259
  buttonIconOf,
3128
3260
  buttonIconSvg,
3129
3261
  buttonLinkPairs,
3262
+ buttonLookValue,
3263
+ buttonSiteLookOf,
3130
3264
  buttonValueOfInstance,
3131
3265
  buttonValueProblem,
3132
3266
  buttonVariantOf,
@@ -3145,10 +3279,18 @@ export {
3145
3279
  hideOnAttr,
3146
3280
  inWindow,
3147
3281
  isBlock,
3282
+ isButtonLookValue,
3148
3283
  isChromeLandmark,
3284
+ isLinkObject,
3285
+ isScriptHref,
3149
3286
  keyWords,
3150
3287
  landmarkRegions,
3288
+ linkAnchorOf,
3289
+ linkHref,
3290
+ linkRefOf,
3291
+ linkValueProblem,
3151
3292
  locatorFor,
3293
+ normalizeLinkValue,
3152
3294
  normalizeWindow,
3153
3295
  parsePropsAttribute,
3154
3296
  pickFieldStyle,