@bettercms-ai/types 1.50.1 → 1.51.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
@@ -1,3 +1,30 @@
1
+ // src/body-schema.ts
2
+ var BODY_SCHEMA_ORIGINS = ["inferred", "confirmed", "authored"];
3
+ var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
4
+ var nonEmpty = (v) => typeof v === "string" && v.length > 0;
5
+ function allowRuleOf(value) {
6
+ if (!isRecord(value)) return null;
7
+ switch (value.kind) {
8
+ case "source":
9
+ return nonEmpty(value.pageId) ? { kind: "source", pageId: value.pageId } : null;
10
+ case "component":
11
+ return nonEmpty(value.id) ? { kind: "component", id: value.id } : null;
12
+ case "family":
13
+ return nonEmpty(value.sectionType) ? { kind: "family", sectionType: value.sectionType } : null;
14
+ case "builtins":
15
+ return { kind: "builtins" };
16
+ case "workspace":
17
+ return { kind: "workspace" };
18
+ default:
19
+ return null;
20
+ }
21
+ }
22
+ function bodySchemaOf(value) {
23
+ if (!isRecord(value) || !Array.isArray(value.allowed)) return null;
24
+ const origin = BODY_SCHEMA_ORIGINS.includes(value.origin) ? value.origin : "authored";
25
+ return { allowed: value.allowed.map(allowRuleOf).filter((r) => r !== null), origin };
26
+ }
27
+
1
28
  // src/block.ts
2
29
  var WINDOW_INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})$/;
3
30
  function normalizeWindow(value) {
@@ -197,14 +224,14 @@ var BLOCK_BINDINGS = {
197
224
  ],
198
225
  tabs: [{ path: "tabs[].label", kind: "text" }]
199
226
  };
200
- var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
227
+ var isRecord2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
201
228
  function readPath(root, path) {
202
229
  let cursor = root;
203
230
  for (const segment of path.split(".")) {
204
231
  if (!segment) return void 0;
205
232
  const key = segment.replace(/\[\d+\]/g, "");
206
233
  if (key) {
207
- if (!isRecord(cursor)) return void 0;
234
+ if (!isRecord2(cursor)) return void 0;
208
235
  cursor = cursor[key];
209
236
  }
210
237
  for (const index of segment.matchAll(/\[(\d+)\]/g)) {
@@ -229,7 +256,7 @@ function expandBlockBindings(block) {
229
256
  const declared = BLOCK_BINDINGS[block.type];
230
257
  if (!declared) return [];
231
258
  const props = block.props;
232
- if (!isRecord(props)) return [];
259
+ if (!isRecord2(props)) return [];
233
260
  return declared.flatMap((binding) => expandTemplate(binding, props, binding.path)).filter((binding) => readPath(props, binding.path) !== void 0);
234
261
  }
235
262
  function formatPropsAttribute(entries) {
@@ -612,8 +639,8 @@ function slotItems(value) {
612
639
  // src/slot-child.ts
613
640
  var SLOT_CHILD_KEYS = ["_key", "_type", "_variant", "_settings"];
614
641
  var RESERVED = new Set(SLOT_CHILD_KEYS);
615
- var isRecord2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
616
- var isSlotChild = (v) => isRecord2(v) && typeof v._type === "string" && v._type !== "";
642
+ var isRecord3 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
643
+ var isSlotChild = (v) => isRecord3(v) && typeof v._type === "string" && v._type !== "";
617
644
  function slotChildValues(child) {
618
645
  const out = {};
619
646
  for (const [k, v] of Object.entries(child)) if (!RESERVED.has(k)) out[k] = v;
@@ -628,7 +655,7 @@ function slotShapeProblems(value, opts = {}) {
628
655
  if (opts.min !== void 0 && value.length < opts.min) out.push({ path: [], code: "slot_too_few", message: `slot_too_few: ${label} needs at least ${opts.min} blocks, got ${value.length}` });
629
656
  const keys = /* @__PURE__ */ new Set();
630
657
  value.forEach((child, i) => {
631
- if (!isRecord2(child)) {
658
+ if (!isRecord3(child)) {
632
659
  out.push({ path: [i], code: "slot_value_invalid", message: `slot_value_invalid: ${label}: block ${i + 1} must be an object { _key, _type, ...values }` });
633
660
  return;
634
661
  }
@@ -646,7 +673,7 @@ function slotShapeProblems(value, opts = {}) {
646
673
  }
647
674
  if (child._settings !== void 0) {
648
675
  if (!opts.settings) out.push({ path: [i, "_settings"], code: "slot_settings_undeclared", message: `slot_settings_undeclared: ${label} declares no slotSettings, so a block holds no _settings` });
649
- else if (!isRecord2(child._settings)) out.push({ path: [i, "_settings"], code: "slot_value_invalid", message: `slot_value_invalid: ${label}: _settings must be an object keyed by the slot's slotSettings` });
676
+ else if (!isRecord3(child._settings)) out.push({ path: [i, "_settings"], code: "slot_value_invalid", message: `slot_value_invalid: ${label}: _settings must be an object keyed by the slot's slotSettings` });
650
677
  }
651
678
  for (const k of Object.keys(child)) {
652
679
  if (k.startsWith("_") && !RESERVED.has(k)) {
@@ -3132,6 +3159,138 @@ var DOCK_ICON_NAMES = [
3132
3159
  ];
3133
3160
  var DOCK_ICON_NAME_SET = new Set(DOCK_ICON_NAMES);
3134
3161
 
3162
+ // src/help-catalog.ts
3163
+ var HELP_MAX = 160;
3164
+ var ROLE_HELP = {
3165
+ // A top-level field's role in its section.
3166
+ headline: "The page's main heading, one per page.",
3167
+ heading: "The heading of this section.",
3168
+ subheading: "Shown under the heading.",
3169
+ eyebrow: "Short label shown above the heading.",
3170
+ intro: "Shown under the heading. One or two sentences.",
3171
+ paragraph: "A paragraph of text.",
3172
+ text: "Text shown on the page.",
3173
+ "primary-button": "The main call to action.",
3174
+ "secondary-button": "The second call to action.",
3175
+ button: "Its text, where it goes and how it looks.",
3176
+ "button-label": "Text on the button. Two to four words works best.",
3177
+ link: "Text of this link. Say where it leads.",
3178
+ "home-link": "Takes visitors back to the home page.",
3179
+ destination: "Pick a page or paste a full URL.",
3180
+ image: "An image shown on the page.",
3181
+ icon: "Optional icon before the label.",
3182
+ logo: "An SVG or transparent PNG works best.",
3183
+ "background-image": "Text sits on top of it, so a calm image works best.",
3184
+ "video-poster": "Shown before the video plays.",
3185
+ video: "Paste a link or upload an MP4.",
3186
+ embed: "Paste the link the other site gives you to share it.",
3187
+ audio: "Paste a link or upload an audio file.",
3188
+ body: "Headings, paragraphs, lists and links, edited as one.",
3189
+ content: "Headings, paragraphs, lists and links, edited as one.",
3190
+ copyright: "Shown at the bottom of every page.",
3191
+ email: "An email address, such as name@example.com.",
3192
+ phone: "A phone number, with its country code.",
3193
+ price: "Whole number, no currency symbol.",
3194
+ percentage: "A number with its % sign.",
3195
+ number: "A figure shown on the page.",
3196
+ date: "A date, written as it should appear.",
3197
+ "stat-value": "A figure shown large, such as 70k+.",
3198
+ "stat-label": "What the figure counts.",
3199
+ quote: "Without quote marks.",
3200
+ attribution: "Who said it.",
3201
+ caption: "Shown under the image.",
3202
+ label: "A short label.",
3203
+ summary: "Shown in lists and previews.",
3204
+ address: "The postal address, as it should appear.",
3205
+ code: "Shown exactly as written.",
3206
+ term: "The word or phrase being explained.",
3207
+ definition: "What the term means.",
3208
+ "table-heading": "Heading of a table column.",
3209
+ "table-cell": "One cell of the table.",
3210
+ "list-item": "One item of the list.",
3211
+ list: "One item per row.",
3212
+ links: "One link per row.",
3213
+ band: "All the text of this section, edited as one.",
3214
+ // A column's role in its row.
3215
+ title: "The title of this item.",
3216
+ subtitle: "Shown under the title.",
3217
+ description: "One line under the name.",
3218
+ value: "The value shown for this item.",
3219
+ // What a band's shape calls an element.
3220
+ "plan-name": "Short name of this plan, shown at the top of its card.",
3221
+ cents: "Digits after the decimal point.",
3222
+ "billing-period": "How often the price is charged.",
3223
+ // The parts of a plan's price object.
3224
+ amount: "Whole number, no currency symbol.",
3225
+ period: "How often the price is charged.",
3226
+ badge: "Optional. Two or three words that make this plan stand out.",
3227
+ features: "One feature per row.",
3228
+ question: "One question, as a customer would ask it.",
3229
+ answer: "Shown when the question is opened.",
3230
+ author: "The person's full name.",
3231
+ role: "Their job title and company.",
3232
+ // What an attribute is, by what carries it.
3233
+ tooltip: "Shown when the pointer rests on it.",
3234
+ placeholder: "Shown in the empty input.",
3235
+ "image-description": "Read by screen readers, not shown on the page.",
3236
+ "counter-target": "The number the counter runs up to.",
3237
+ attribute: "A setting of this element, not shown as text.",
3238
+ "navigation-label": "Read by screen readers, not shown on the page.",
3239
+ "link-label": "Read by screen readers, not shown on the page.",
3240
+ "image-label": "Read by screen readers, not shown on the page.",
3241
+ "field-label": "Read by screen readers, not shown on the page.",
3242
+ "header-label": "Read by screen readers, not shown on the page.",
3243
+ "footer-label": "Read by screen readers, not shown on the page.",
3244
+ "aside-label": "Read by screen readers, not shown on the page.",
3245
+ "section-label": "Read by screen readers, not shown on the page.",
3246
+ "heading-label": "Read by screen readers, not shown on the page.",
3247
+ // What a list's rows are.
3248
+ plans: "One plan per card.",
3249
+ questions: "One question and its answer per row.",
3250
+ quotes: "One quote per row, with who said it.",
3251
+ testimonials: "One quote per row, with who said it.",
3252
+ stats: "One figure per row, with what it counts.",
3253
+ logos: "One logo per row.",
3254
+ cards: "One card per row.",
3255
+ items: "One item per row."
3256
+ };
3257
+ var CANVAS_GESTURE_NAMES = ["rows.reorder", "image.replace", "link.popover", "text.inline"];
3258
+ var CONTROL_HELP = {
3259
+ rows: { text: "Drag to reorder. You can also drag rows on the canvas.", gesture: "rows.reorder" },
3260
+ image: { text: "You can also click it on the canvas to replace it.", gesture: "image.replace" },
3261
+ link: { text: "Click it on the canvas to change where it goes.", gesture: "link.popover" },
3262
+ text: { text: "You can also edit it on the canvas.", gesture: "text.inline" }
3263
+ };
3264
+ var EXAMPLE_MAX = 40;
3265
+ function sayOf(value) {
3266
+ if (typeof value === "number") return String(value);
3267
+ const v = value;
3268
+ const raw = typeof value === "string" ? value : typeof v?.html === "string" ? v.html : typeof v?.label === "string" ? v.label : typeof v?.text === "string" ? v.text : "";
3269
+ return raw.replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim();
3270
+ }
3271
+ var quotable = (s) => s !== "" && s.length <= EXAMPLE_MAX && !s.includes('"');
3272
+ function exampleFor(column, rows, i) {
3273
+ const at = (j) => {
3274
+ const row = rows[j];
3275
+ return row && typeof row === "object" ? sayOf(row[column]) : "";
3276
+ };
3277
+ const own2 = at(i);
3278
+ for (let d = 1; d < rows.length; d += 1) {
3279
+ for (const j of [i - d, i + d]) {
3280
+ if (j < 0 || j >= rows.length) continue;
3281
+ const v = at(j);
3282
+ if (v !== own2 && quotable(v)) return `e.g. "${v}"`;
3283
+ }
3284
+ }
3285
+ return quotable(own2) ? `e.g. "${own2}"` : void 0;
3286
+ }
3287
+ function withExample(help, example) {
3288
+ if (!example) return help;
3289
+ if (!help) return example;
3290
+ const joined = `${help} ${example}`;
3291
+ return joined.length <= HELP_MAX ? joined : help;
3292
+ }
3293
+
3135
3294
  // src/dock-budget.ts
3136
3295
  var DOCK_BUDGET = { ungrouped: 8, group: 12, depth: 3, activations: 2 };
3137
3296
  function listItems(f) {
@@ -3152,10 +3311,10 @@ function layout(fields, fieldsets, root) {
3152
3311
  const byId = new Map(fieldsets.map((s) => [s.id, s]));
3153
3312
  const tabIds = root ? fieldsets.filter((s) => s.kind === "tab").map((s) => s.id) : [];
3154
3313
  const tabOf = (s) => s?.kind === "tab" ? s.id : s?.tab !== void 0 && byId.get(s.tab)?.kind === "tab" ? s.tab : void 0;
3155
- const nonEmpty = new Set(
3314
+ const nonEmpty2 = new Set(
3156
3315
  fields.map((f) => tabOf(byId.get(f.fieldsetId ?? ""))).filter((t) => t && tabIds.includes(t))
3157
3316
  );
3158
- const strip2 = nonEmpty.size >= 2;
3317
+ const strip2 = nonEmpty2.size >= 2;
3159
3318
  const branches = /* @__PURE__ */ new Map();
3160
3319
  const nodeFor = (f, path) => {
3161
3320
  const key = `${path}${f.key}`;
@@ -3176,7 +3335,7 @@ function layout(fields, fieldsets, root) {
3176
3335
  return { kind: "leaf", key };
3177
3336
  };
3178
3337
  const pinned = [];
3179
- const panels = new Map(strip2 ? tabIds.filter((t) => nonEmpty.has(t)).map((t) => [t, []]) : []);
3338
+ const panels = new Map(strip2 ? tabIds.filter((t) => nonEmpty2.has(t)).map((t) => [t, []]) : []);
3180
3339
  const containers = /* @__PURE__ */ new Map();
3181
3340
  for (const f of fields) {
3182
3341
  const node = nodeFor(f, "");
@@ -3381,6 +3540,19 @@ function buttonLinkPairs(candidates) {
3381
3540
  return [...proposals.values()].filter(([t, l]) => !ambiguous.has(t) && !ambiguous.has(l) && uses.get(t) === 1 && uses.get(l) === 1).sort((a, b) => Math.min(...a) - Math.min(...b));
3382
3541
  }
3383
3542
 
3543
+ // src/link-pairs.ts
3544
+ var LINK_TYPES2 = /* @__PURE__ */ new Set(["link", "url"]);
3545
+ var nameOf = (s) => s.name ?? s.key;
3546
+ function rulePartnerOf(labelKey, siblings) {
3547
+ const names = labelKey === "label" ? ["label-href", "href", "url", "link"] : [`${labelKey}-href`];
3548
+ for (const n of names) {
3549
+ const s = siblings.find((f) => nameOf(f) === n);
3550
+ if (s && LINK_TYPES2.has(s.type ?? "")) return n;
3551
+ }
3552
+ return null;
3553
+ }
3554
+ var isRulePaired = (labelKey, siblings) => rulePartnerOf(labelKey, siblings) !== null;
3555
+
3384
3556
  // src/button-look.ts
3385
3557
  import { ICON_MARKUP, iconSvg } from "@bettercms-ai/icons/markup";
3386
3558
 
@@ -4184,6 +4356,14 @@ function locatorFor(el, region, landmarks = false) {
4184
4356
  tag: el.tagName.toLowerCase()
4185
4357
  };
4186
4358
  }
4359
+ function locatorPath(el, landmarks = false) {
4360
+ const path = [];
4361
+ for (let parent = el.parentElement; parent && parent.tagName !== "BODY" && !isChromeLandmark(parent, landmarks); parent = parent.parentElement) {
4362
+ path.unshift(parent.parentElement ? Math.max(Array.from(parent.parentElement.children).indexOf(parent), 0) : 0);
4363
+ }
4364
+ return path;
4365
+ }
4366
+ var LOCATOR_PATH = /* @__PURE__ */ Symbol.for("bcms.locator.path");
4187
4367
  function landmarkRegions(doc, dialogs = false, landmarks = false) {
4188
4368
  const counts = /* @__PURE__ */ new Map();
4189
4369
  const out = [];
@@ -4244,9 +4424,81 @@ var TAG_HEADS = /* @__PURE__ */ new Set([
4244
4424
  "icon",
4245
4425
  "bg"
4246
4426
  ]);
4427
+ var ROLE_KEYS = /* @__PURE__ */ new Set([
4428
+ "headline",
4429
+ "heading",
4430
+ "subheading",
4431
+ "eyebrow",
4432
+ "intro",
4433
+ "paragraph",
4434
+ "text",
4435
+ "primary-button",
4436
+ "secondary-button",
4437
+ "button",
4438
+ "link",
4439
+ "home-link",
4440
+ "image",
4441
+ "icon",
4442
+ "logo",
4443
+ "background-image",
4444
+ "video-poster",
4445
+ "video",
4446
+ "embed",
4447
+ "audio",
4448
+ "body",
4449
+ "content",
4450
+ "copyright",
4451
+ "email",
4452
+ "phone",
4453
+ "price",
4454
+ "percentage",
4455
+ "number",
4456
+ "date",
4457
+ "stat-value",
4458
+ "stat-label",
4459
+ "quote",
4460
+ "attribution",
4461
+ "caption",
4462
+ "label",
4463
+ "summary",
4464
+ "address",
4465
+ "code",
4466
+ "term",
4467
+ "definition",
4468
+ "table-heading",
4469
+ "table-cell",
4470
+ "list-item",
4471
+ "title",
4472
+ "subtitle",
4473
+ "description",
4474
+ "value",
4475
+ "list",
4476
+ "plan-name",
4477
+ "cents",
4478
+ "billing-period",
4479
+ "badge",
4480
+ "features",
4481
+ "question",
4482
+ "answer",
4483
+ "author",
4484
+ "role",
4485
+ "tooltip",
4486
+ "placeholder",
4487
+ "image-description",
4488
+ "counter-target",
4489
+ "amount",
4490
+ "period"
4491
+ ]);
4492
+ var ROLE_TAILS = /(?:-label)?(?:-[a-z])?(?:-\d+)?(?:-href)?$/;
4493
+ function isRoleKey(key) {
4494
+ if (ROLE_KEYS.has(key)) return true;
4495
+ const tail = ROLE_TAILS.exec(key);
4496
+ return !!tail && tail[0] !== "" && ROLE_KEYS.has(key.slice(0, tail.index));
4497
+ }
4247
4498
  var topFieldKeyOf = (path) => path.split(/[.[]/, 1)[0] ?? path;
4248
4499
  function fieldGroupOf(topKey, flat = false) {
4249
4500
  if (topKey.startsWith("group-")) return { groupKey: topKey, rooted: true };
4501
+ if (isRoleKey(topKey)) return { groupKey: topKey, rooted: false };
4250
4502
  const camel = flat ? /^([a-z][a-z0-9]*)[A-Z]/.exec(topKey)?.[1] : void 0;
4251
4503
  if (camel) return TAG_HEADS.has(camel) ? { groupKey: topKey, rooted: false } : { groupKey: camel, rooted: true };
4252
4504
  const head = topKey.split(/[-_]/, 1)[0] || topKey;
@@ -4300,6 +4552,7 @@ function planSections(slots, blocks) {
4300
4552
  }
4301
4553
  export {
4302
4554
  BLOCK_BINDINGS,
4555
+ BODY_SCHEMA_ORIGINS,
4303
4556
  BREAKPOINTS,
4304
4557
  BUTTON_ACTIONS,
4305
4558
  BUTTON_FULL_WIDTH_INVALID,
@@ -4309,8 +4562,10 @@ export {
4309
4562
  BUTTON_PLACEMENT_KEYS,
4310
4563
  BUTTON_SIZES,
4311
4564
  BUTTON_VARIANTS,
4565
+ CANVAS_GESTURE_NAMES,
4312
4566
  CODE_SLOT_PREFIX,
4313
4567
  COMPONENT_PROP_TYPES,
4568
+ CONTROL_HELP,
4314
4569
  DIALOG_SELECTOR,
4315
4570
  DOCK_BUDGET,
4316
4571
  DOCK_ICON_NAMES,
@@ -4318,6 +4573,7 @@ export {
4318
4573
  FIELD_SIZE_ZOOM,
4319
4574
  FIELD_STYLE_KEYS,
4320
4575
  FOOTER_SECTION_ID,
4576
+ HELP_MAX,
4321
4577
  IMAGE_ASPECTS,
4322
4578
  IMAGE_FRAME_RADIUS,
4323
4579
  LANDMARK_SELECTOR,
@@ -4326,11 +4582,14 @@ export {
4326
4582
  LINK_RELS,
4327
4583
  LINK_VALUE_TYPES,
4328
4584
  LIST_ITEMS_CAP,
4585
+ LOCATOR_PATH,
4329
4586
  MAIN_SELECTOR,
4330
4587
  NAVIGATION_SECTION_ID,
4331
4588
  OPTION_DESCRIPTION_MAX,
4332
4589
  PAGE_CONTENT_ANCHOR_ID,
4333
4590
  RESPONSIVE_CSS,
4591
+ ROLE_HELP,
4592
+ ROLE_KEYS,
4334
4593
  SECTION_DOCTRINE,
4335
4594
  SECTION_SPACE_TOKENS,
4336
4595
  SIDE_SPACE_MIN_PX,
@@ -4347,10 +4606,12 @@ export {
4347
4606
  TAG_HEADS,
4348
4607
  VIDEO_TRANSCRIPT_MAX,
4349
4608
  VIDEO_VALUE_KEYS,
4609
+ allowRuleOf,
4350
4610
  anchorTargetRel,
4351
4611
  blockStyleToColumnCss,
4352
4612
  blockStyleToCss,
4353
4613
  blockStyleToInnerCss,
4614
+ bodySchemaOf,
4354
4615
  buttonBlockProps,
4355
4616
  buttonFullWidthProblem,
4356
4617
  buttonIconOf,
@@ -4367,6 +4628,7 @@ export {
4367
4628
  derivedPairs,
4368
4629
  dockBudget,
4369
4630
  dockScreens,
4631
+ exampleFor,
4370
4632
  expandBlockBindings,
4371
4633
  fieldGroupOf,
4372
4634
  fieldStyleCssText,
@@ -4384,6 +4646,8 @@ export {
4384
4646
  isChromeLandmark,
4385
4647
  isCodeButton,
4386
4648
  isLinkObject,
4649
+ isRoleKey,
4650
+ isRulePaired,
4387
4651
  isScriptHref,
4388
4652
  isSlotChild,
4389
4653
  isTableValue,
@@ -4394,6 +4658,7 @@ export {
4394
4658
  linkRefOf,
4395
4659
  linkValueProblem,
4396
4660
  locatorFor,
4661
+ locatorPath,
4397
4662
  normalizeLinkValue,
4398
4663
  normalizeTableValue,
4399
4664
  normalizeWindow,
@@ -4407,6 +4672,7 @@ export {
4407
4672
  readPath,
4408
4673
  regionOf,
4409
4674
  renderTableHtml,
4675
+ rulePartnerOf,
4410
4676
  safeCssUrl,
4411
4677
  sideFloorApplies,
4412
4678
  sideSpaceCss,
@@ -4431,6 +4697,7 @@ export {
4431
4697
  visibleDeliveredLayout,
4432
4698
  visibleLayoutStructure,
4433
4699
  windowState,
4434
- withButtonLookProps
4700
+ withButtonLookProps,
4701
+ withExample
4435
4702
  };
4436
4703
  //# sourceMappingURL=index.js.map