@bettercms-ai/types 1.40.0 → 1.42.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
@@ -70,6 +70,91 @@ function getBlockType(value) {
70
70
  return null;
71
71
  }
72
72
 
73
+ // src/number-format.ts
74
+ var GROUPS = [",", ".", " ", "\xA0", "\u202F", "'", "\u2019"];
75
+ var COMMA_DECIMAL = /^(de|es|it|nl|pt|id|tr|da|fr|sv|nb|no|fi|pl|cs|ru|uk|el|ro|hu|sk|sl|hr|bg)\b/i;
76
+ var SPACE_GROUP = /^(fr|sv|nb|no|fi|pl|cs|ru|uk|sk|hu|bg)\b/i;
77
+ var UNIT = "(?:[kKmMbBxsdgh]|bn|hr|hrs|min|ms|kg|mg|lb|lbs|oz|km|cm|mm|mi|ft|[kKMGT]B|px|pt|\xB0[CF]?)";
78
+ var SUFFIX = new RegExp(`^(?:\\s?(?:\\p{Sc}|[%\u2030\xD7]|${UNIT}))?\\+?(?:\\s?/\\s?\\p{L}{1,10}){0,2}[)\\]*\u2020]?$`, "u");
79
+ var PREFIX = /^(?:[^\p{L}\p{N}\s.,'\u2019#№\-−–—]{1,3}\s?)?$/u;
80
+ var fractionDigits = (value) => {
81
+ const text = value.toFixed(6).replace(/0+$/, "");
82
+ return text.length - text.indexOf(".") - 1;
83
+ };
84
+ function readNumberFormat(raw) {
85
+ const o = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
86
+ const text = (v, max) => typeof v === "string" && v.length <= max && !new RegExp("\\p{Nd}", "u").test(v) ? v : void 0;
87
+ const prefix = text(o.prefix, 8);
88
+ const suffix = text(o.suffix, 24);
89
+ const group = typeof o.group === "string" && GROUPS.includes(o.group) ? o.group : void 0;
90
+ const decimal = o.decimal === "," ? "," : void 0;
91
+ const decimals = typeof o.decimals === "number" && Number.isInteger(o.decimals) && o.decimals > 0 && o.decimals <= 6 ? o.decimals : void 0;
92
+ return {
93
+ ...prefix ? { prefix } : {},
94
+ ...suffix ? { suffix } : {},
95
+ // A group mark that is also the decimal mark would print a number nobody can read back.
96
+ ...group && group !== (decimal ?? ".") ? { group } : {},
97
+ ...decimal ? { decimal } : {},
98
+ ...decimals ? { decimals } : {}
99
+ };
100
+ }
101
+ function formatNumber(value, format = {}) {
102
+ if (typeof value !== "number" || !Number.isFinite(value)) return "";
103
+ const places = Math.max(format.decimals ?? 0, Math.min(6, fractionDigits(value)));
104
+ const [int, frac] = Math.abs(value).toFixed(places).split(".");
105
+ const grouped = format.group ? int.replace(/\B(?=(\d{3})+(?!\d))/g, format.group) : int;
106
+ return `${value < 0 ? "-" : ""}${format.prefix ?? ""}${grouped}${frac ? `${format.decimal ?? "."}${frac}` : ""}${format.suffix ?? ""}`;
107
+ }
108
+ function parseNumberText(text, opts = {}) {
109
+ if (typeof text !== "string" || text.length === 0 || text.length > 40) return null;
110
+ const digits = /\d/.exec(text);
111
+ if (!digits) return null;
112
+ const start = digits.index;
113
+ const body = /^\d[\d.,'\u2019 \u00a0\u202f]*\d|^\d/.exec(text.slice(start))[0];
114
+ const prefix = text.slice(0, start);
115
+ const suffix = text.slice(start + body.length);
116
+ if (/\d/.test(suffix) || !PREFIX.test(prefix) || !SUFFIX.test(suffix)) return null;
117
+ if (opts.machine && (prefix || suffix || !/^\d+(\.\d+)?$/.test(body))) return null;
118
+ if (/[)\]]$/.test(suffix) !== /^[([]/.test(prefix)) return null;
119
+ const commaLang = COMMA_DECIMAL.test(opts.lang ?? "");
120
+ const marks = [...body].filter((c) => !/\d/.test(c));
121
+ const kinds = [...new Set(marks)];
122
+ let group;
123
+ let decimal;
124
+ if (kinds.length > 2) return null;
125
+ if (kinds.length === 2) {
126
+ const last = marks[marks.length - 1];
127
+ if (last !== "." && last !== "," || marks.filter((m) => m === last).length !== 1) return null;
128
+ decimal = last;
129
+ group = kinds.find((k) => k !== last);
130
+ } else if (kinds.length === 1) {
131
+ const mark = kinds[0];
132
+ const tail = body.slice(body.lastIndexOf(mark) + 1);
133
+ const lone = marks.length === 1 && (mark === "." || mark === ",");
134
+ const asDecimal = lone && (tail.length !== 3 || body.indexOf(mark) > 3 || /^0/.test(body) || (mark === "." ? !commaLang : commaLang));
135
+ if (asDecimal) decimal = mark;
136
+ else group = mark;
137
+ }
138
+ if (group !== void 0 && group === decimal || group && !GROUPS.includes(group)) return null;
139
+ const [intPart, frac = ""] = decimal ? body.split(decimal) : [body];
140
+ const whole = group ? intPart.split(group) : [intPart];
141
+ if (group && (whole.length < 2 || !/^[1-9]\d{0,2}$/.test(whole[0]) || whole.slice(1).some((g) => !/^\d{3}$/.test(g)))) return null;
142
+ const int = whole.join("");
143
+ if (!/^\d+$/.test(int) || int.length > 1 && int.startsWith("0") || int.length + frac.length > 15 || decimal && !/^\d+$/.test(frac)) return null;
144
+ const value = Number(frac ? `${int}.${frac}` : int);
145
+ if (!group && !opts.machine && int.length < 4) group = SPACE_GROUP.test(opts.lang ?? "") ? "\xA0" : commaLang ? "." : ",";
146
+ if (!decimal && (group === "." || commaLang && !opts.machine)) decimal = ",";
147
+ if (group === (decimal ?? ".")) group = void 0;
148
+ const format = {
149
+ ...prefix ? { prefix } : {},
150
+ ...suffix ? { suffix } : {},
151
+ ...group ? { group } : {},
152
+ ...decimal === "," ? { decimal } : {},
153
+ ...frac ? { decimals: frac.length } : {}
154
+ };
155
+ return Number.isFinite(value) && frac.length <= 6 && formatNumber(value, format) === text ? { value, format } : null;
156
+ }
157
+
73
158
  // src/block-bindings.ts
74
159
  var BLOCK_BINDINGS = {
75
160
  heading: [{ path: "text", kind: "text" }],
@@ -406,7 +491,7 @@ function fieldStyleCssText(style) {
406
491
  }
407
492
 
408
493
  // src/component.ts
409
- var SECTION_DOCTRINE = "STRUCTURE (separate from schema): a page is composed of SECTIONS. NEVER build a page out of loose top-level heading/text/image/button/spacer blocks \u2014 they cannot be moved, duplicated or swapped as a unit, the visual editor cannot outline or name them, and every one of them becomes its own section in the editor. A hero of a headline, a lede and two CTAs is ONE section, not four. TWO SHAPES, and the choice is about REUSE. (1) A band that appears on more than one page, or that needs layout variants, is a COMPONENT with a `sectionType` \u2014 see create_component. Components sharing a `sectionType` are that section's VARIANTS (one Hero: 'Centered' for the home page and 'Two-column' for about, same prop keys so a swap keeps the content). This is also the only shape the editor's 'Add a section' picker can insert, and the only one that gets a family name and a variant switcher. (2) A genuinely one-off band on a single page is a `section` BLOCK whose `props.children` hold its blocks. THE TRADEOFF, stated in the present tense because it is real today: inside a component, ONLY the leaves a declared prop TARGETS are click-to-edit on the canvas \u2014 each declared prop's `target` is re-keyed to that PLACEMENT's own override address, so editing it changes this page and not the shared definition. A prop with no `target` still SHOWS as a dock control, but its override reaches no rendered slot, so editing it changes nothing on the page. Copy that no prop points at is worse still: no binding and no control, unreachable from click-to-edit AND from the dock, changeable only by editing the component definition, which rewrites every page that places it. A `section` block's children stay click-to-edit unconditionally. So when you choose a component, DECLARE A PROP \u2014 WITH A `target` \u2014 for every string, link and image a marketer will ever touch; a component with un-propped editable copy is the defect, not the component. In the dock an unset prop shows EMPTY and inherits the definition's default, so set props explicitly when you want the current copy visible there. An IMPORTED site's components are its own (read the repo); its nav, footer and modals are the Layout. On a blank site, do not hand-write a band's JSON: start from a built-in section blueprint (list_components returns locked `builtin:*` blueprints with no projectId \u2014 hero-centered, hero-split, feature-grid-three, cta-banner and nine more), each already rooted in a `section` block with its editable leaves declared as props. INLINE its blockJson as a `section` block for a one-off band; for a recurring band, materialize the blueprint with create_component so it becomes project-scoped before implementation validation, Output or publication. Direct `builtin:*` component references exist only for legacy delivery compatibility. Two consecutive call-to-action buttons are two sibling `button` blocks inside the same section \u2014 never a `columns` block, which is a `repeat(N,1fr)` grid and would stretch each CTA to half the container. Buttons are inline-level and flow side by side on their own.";
494
+ var SECTION_DOCTRINE = "STRUCTURE (separate from schema): a page is composed of SECTIONS. NEVER build a page out of loose top-level heading/text/image/button/spacer blocks \u2014 they cannot be moved, duplicated or swapped as a unit, the visual editor cannot outline or name them, and every one of them becomes its own section in the editor. A hero of a headline, a lede and two CTAs is ONE section, not four. TWO SHAPES, and the choice is about REUSE. (1) A band that appears on more than one page, or that needs layout variants, is a COMPONENT with a `sectionType` \u2014 see create_component. Components sharing a `sectionType` are that section's VARIANTS (one Hero: 'Centered' for the home page and 'Two-column' for about, same prop keys so a swap keeps the content). This is also the only shape the editor's 'Add a section' picker can insert, and the only one that gets a family name and a variant switcher. (2) A genuinely one-off band on a single page is a `section` BLOCK whose `props.children` hold its blocks. THE TRADEOFF, stated in the present tense because it is real today: inside a component, ONLY the leaves a declared prop TARGETS are click-to-edit on the canvas \u2014 each declared prop's `target` is re-keyed to that PLACEMENT's own override address, so editing it changes this page and not the shared definition. A prop with no `target` still SHOWS as a dock control, but its override reaches no rendered slot, so editing it changes nothing on the page. Copy that no prop points at is worse still: no binding and no control, unreachable from click-to-edit AND from the dock, changeable only by editing the component definition, which rewrites every page that places it. A `section` block's children stay click-to-edit unconditionally. So when you choose a component, DECLARE A PROP \u2014 WITH A `target` \u2014 for every string, link and image a marketer will ever touch; a component with un-propped editable copy is the defect, not the component. In the dock an unset prop shows EMPTY and inherits the definition's default, so set props explicitly when you want the current copy visible there. An IMPORTED site's components are its own (read the repo); its nav, footer and modals are the Layout. On a blank site, do not hand-write a band's JSON: start from a built-in section blueprint (list_components returns locked `builtin:*` blueprints with no projectId \u2014 hero-centered, hero-split, feature-grid-three, cta-banner and more), each already rooted in a `section` block with its editable leaves declared as props. INLINE its blockJson as a `section` block for a one-off band; for a recurring band, materialize it with create_component `from` so it becomes project-scoped before implementation validation, Output or publication. Direct `builtin:*` component references exist only for legacy delivery compatibility. Two consecutive call-to-action buttons are two sibling `button` blocks inside the same section \u2014 never a `columns` block, which is a `repeat(N,1fr)` grid and would stretch each CTA to half the container. Buttons are inline-level and flow side by side on their own.";
410
495
  var COMPONENT_PROP_TYPES = [
411
496
  "text",
412
497
  "richtext",
@@ -427,16 +512,95 @@ var COMPONENT_PROP_TYPES = [
427
512
  "color",
428
513
  "file",
429
514
  "icon",
430
- "multiselect"
515
+ "multiselect",
516
+ // V2 schema language (F1.x). `object` is ONE nested value of named `config.fields`, stored as
517
+ // `{ [childKey]: value }` like the model field `object`; the six after it store what the
518
+ // same-named content-model FIELD stores (`location` is the V2 geopoint) and are judged by
519
+ // that field's validator.
520
+ "object",
521
+ "datetime",
522
+ "slug",
523
+ "email",
524
+ "phone",
525
+ "json",
526
+ "location",
527
+ // V2 leaves, each storing what the same-named content-model FIELD stores: a taxonomy string[],
528
+ // a rating number, a code value (`{ language?, code }` or a string).
529
+ "tags",
530
+ "rating",
531
+ "code",
532
+ // CMS items, as the same-named content-model FIELDS store them: an entry id (or a polymorphic
533
+ // `{ entryId, modelSlug }`), and a list of them. Delivery hydrates them like an entry's.
534
+ "reference",
535
+ "multi-reference",
536
+ // V2 chrome types, each storing what the same-named content-model FIELD stores: a video value,
537
+ // a primitive list (`config.itemType`), and multi-line text (the field lane's V2 `text`).
538
+ "video",
539
+ "array",
540
+ "longtext"
431
541
  ];
432
542
  var SLOT_MAX_ITEMS = 12;
433
543
  var SLOT_ITEMS_CAP = 50;
434
544
  function slotItems(value) {
435
545
  const list = Array.isArray(value) ? value : [value];
436
- return list.filter(
437
- (item) => !!item && typeof item === "object" && !Array.isArray(item) && typeof item.componentId === "string" && item.componentId !== ""
438
- );
546
+ const out = [];
547
+ for (const item of list) {
548
+ if (!item || typeof item !== "object" || Array.isArray(item)) continue;
549
+ const o = item;
550
+ if (typeof o.componentId === "string" && o.componentId !== "") out.push(item);
551
+ else if (typeof o.componentId !== "string" && typeof o._type === "string" && o._type !== "") out.push({ componentId: o._type, overrides: o });
552
+ }
553
+ return out;
554
+ }
555
+
556
+ // src/slot-child.ts
557
+ var SLOT_CHILD_KEYS = ["_key", "_type", "_variant", "_settings"];
558
+ var RESERVED = new Set(SLOT_CHILD_KEYS);
559
+ var isRecord2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
560
+ var isSlotChild = (v) => isRecord2(v) && typeof v._type === "string" && v._type !== "";
561
+ function slotChildValues(child) {
562
+ const out = {};
563
+ for (const [k, v] of Object.entries(child)) if (!RESERVED.has(k)) out[k] = v;
564
+ return out;
565
+ }
566
+ function slotShapeProblems(value, opts = {}) {
567
+ const label = opts.label ?? "slot";
568
+ const noun = opts.typeNoun ?? "a block type";
569
+ if (!Array.isArray(value)) return [{ path: [], code: "slot_value_invalid", message: `slot_value_invalid: ${label} must be a list of blocks ({ _key, _type, ...values })` }];
570
+ const out = [];
571
+ if (opts.max !== void 0 && value.length > opts.max) out.push({ path: [], code: "slot_too_many", message: `slot_too_many: ${label} holds at most ${opts.max} blocks, got ${value.length}` });
572
+ 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}` });
573
+ const keys = /* @__PURE__ */ new Set();
574
+ value.forEach((child, i) => {
575
+ if (!isRecord2(child)) {
576
+ out.push({ path: [i], code: "slot_value_invalid", message: `slot_value_invalid: ${label}: block ${i + 1} must be an object { _key, _type, ...values }` });
577
+ return;
578
+ }
579
+ if (typeof child._key !== "string" || child._key.trim() === "") {
580
+ out.push({ path: [i, "_key"], code: "slot_child_key_required", message: `slot_child_key_required: ${label}: block ${i + 1} needs a _key (a non-empty string)` });
581
+ } else if (keys.has(child._key)) {
582
+ out.push({ path: [i, "_key"], code: "slot_child_key_duplicate", message: `slot_child_key_duplicate: ${label}: _key '${child._key}' is used by two blocks` });
583
+ } else keys.add(child._key);
584
+ if (typeof child._type !== "string" || child._type.trim() === "") {
585
+ out.push({ path: [i, "_type"], code: "slot_child_type_required", message: `slot_child_type_required: ${label}: block ${i + 1} needs a _type (${noun})` });
586
+ }
587
+ if (child._variant !== void 0) {
588
+ if (!opts.variants) out.push({ path: [i, "_variant"], code: "slot_variant_unsupported", message: `slot_variant_unsupported: ${label}: a component variant is its own component; name it in _type` });
589
+ else if (typeof child._variant !== "string" || child._variant === "") out.push({ path: [i, "_variant"], code: "slot_value_invalid", message: `slot_value_invalid: ${label}: _variant must be a variant name` });
590
+ }
591
+ if (child._settings !== void 0) {
592
+ 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` });
593
+ 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` });
594
+ }
595
+ for (const k of Object.keys(child)) {
596
+ if (k.startsWith("_") && !RESERVED.has(k)) {
597
+ out.push({ path: [i, k], code: "slot_child_key_unknown", message: `slot_child_key_unknown: ${label}: '${k}' is not a slot key (${SLOT_CHILD_KEYS.join(", ")}); a block's values have no leading underscore` });
598
+ }
599
+ }
600
+ });
601
+ return out;
439
602
  }
603
+ var slotPathString = (path) => path.map((s, i) => typeof s === "number" ? `[${s}]` : i === 0 ? s : `.${s}`).join("");
440
604
 
441
605
  // src/layout-lucide-icons.ts
442
606
  var LAYOUT_SECTION_ICONS = Object.freeze([
@@ -2437,6 +2601,7 @@ function visibleDeliveredLayout(layout2, now) {
2437
2601
 
2438
2602
  // src/dock-icon-names.ts
2439
2603
  var DOCK_ICON_NAMES = [
2604
+ "abc",
2440
2605
  "access-denied",
2441
2606
  "activity",
2442
2607
  "add",
@@ -2444,14 +2609,40 @@ var DOCK_ICON_NAMES = [
2444
2609
  "add-comment",
2445
2610
  "add-document",
2446
2611
  "add-user",
2612
+ "address-book",
2613
+ "adjustments",
2614
+ "adjustments-horizontal",
2615
+ "alarm",
2616
+ "alert-triangle",
2617
+ "align-left",
2447
2618
  "api",
2619
+ "app-window",
2620
+ "apple",
2621
+ "apps",
2448
2622
  "archive",
2623
+ "arrow-back-up",
2449
2624
  "arrow-down",
2625
+ "arrow-guide",
2450
2626
  "arrow-left",
2451
2627
  "arrow-right",
2452
2628
  "arrow-top-right",
2453
2629
  "arrow-up",
2630
+ "arrows-diagonal",
2631
+ "arrows-down",
2632
+ "arrows-exchange",
2633
+ "arrows-horizontal",
2634
+ "arrows-left-right",
2635
+ "arrows-shuffle",
2636
+ "arrows-sort",
2637
+ "article",
2638
+ "aspect-ratio",
2454
2639
  "asterisk",
2640
+ "award",
2641
+ "axis-x",
2642
+ "backpack",
2643
+ "badge",
2644
+ "badge-cc",
2645
+ "badges",
2455
2646
  "bar-chart",
2456
2647
  "basket",
2457
2648
  "bell",
@@ -2463,25 +2654,76 @@ var DOCK_ICON_NAMES = [
2463
2654
  "bold",
2464
2655
  "bolt",
2465
2656
  "book",
2657
+ "book-2",
2466
2658
  "bookmark",
2467
2659
  "bookmark-filled",
2660
+ "books",
2468
2661
  "bottle",
2662
+ "box",
2663
+ "brand-dribbble",
2664
+ "brand-facebook",
2665
+ "brand-instagram",
2666
+ "brand-linkedin",
2667
+ "brand-pinterest",
2668
+ "brand-tiktok",
2669
+ "brand-x",
2670
+ "brand-youtube",
2671
+ "briefcase",
2672
+ "broadcast",
2673
+ "browser",
2469
2674
  "bug",
2675
+ "building",
2676
+ "building-skyscraper",
2677
+ "building-store",
2470
2678
  "bulb-filled",
2471
2679
  "bulb-outline",
2680
+ "calculator",
2472
2681
  "calendar",
2682
+ "calendar-dollar",
2683
+ "calendar-event",
2684
+ "calendar-exclamation",
2685
+ "calendar-off",
2686
+ "calendar-plus",
2687
+ "calendar-star",
2688
+ "calendar-time",
2689
+ "camera",
2690
+ "campfire",
2691
+ "carousel-horizontal",
2473
2692
  "case",
2693
+ "cash",
2694
+ "category",
2695
+ "category-2",
2696
+ "chart-area",
2697
+ "chart-arrows",
2698
+ "chart-bar",
2699
+ "chart-bar-popular",
2700
+ "chart-donut",
2701
+ "chart-dots",
2702
+ "chart-line",
2474
2703
  "chart-upward",
2704
+ "check",
2705
+ "checkbox",
2706
+ "checklist",
2475
2707
  "checkmark",
2476
2708
  "checkmark-circle",
2709
+ "chef-hat",
2477
2710
  "chevron-down",
2478
2711
  "chevron-left",
2479
2712
  "chevron-right",
2480
2713
  "chevron-up",
2714
+ "chevrons-right",
2481
2715
  "circle",
2716
+ "circle-check",
2717
+ "circle-dot",
2718
+ "circle-number-1",
2719
+ "circle-plus",
2720
+ "click",
2482
2721
  "clipboard",
2483
2722
  "clipboard-image",
2723
+ "clipboard-text",
2484
2724
  "clock",
2725
+ "clock-check",
2726
+ "clock-hour-4",
2485
2727
  "close",
2486
2728
  "close-circle",
2487
2729
  "code",
@@ -2489,20 +2731,31 @@ var DOCK_ICON_NAMES = [
2489
2731
  "cog",
2490
2732
  "collapse",
2491
2733
  "color-wheel",
2734
+ "columns",
2735
+ "columns-2",
2736
+ "columns-3",
2492
2737
  "comment",
2493
2738
  "component",
2494
2739
  "compose",
2495
2740
  "compose-sparkles",
2496
2741
  "confetti",
2497
2742
  "controls",
2743
+ "cookie",
2498
2744
  "copy",
2499
2745
  "credit-card",
2500
2746
  "crop",
2501
2747
  "cube",
2748
+ "currency-dollar",
2502
2749
  "dashboard",
2503
2750
  "database",
2751
+ "database-search",
2504
2752
  "desktop",
2753
+ "device-desktop",
2754
+ "device-mobile",
2755
+ "device-tablet",
2756
+ "devices",
2505
2757
  "diamond",
2758
+ "discount",
2506
2759
  "document",
2507
2760
  "document-pdf",
2508
2761
  "document-remove",
@@ -2512,6 +2765,7 @@ var DOCK_ICON_NAMES = [
2512
2765
  "document-word",
2513
2766
  "document-zip",
2514
2767
  "documents",
2768
+ "door-exit",
2515
2769
  "dot",
2516
2770
  "double-chevron-down",
2517
2771
  "double-chevron-left",
@@ -2521,9 +2775,12 @@ var DOCK_ICON_NAMES = [
2521
2775
  "download",
2522
2776
  "drag-handle",
2523
2777
  "drop",
2778
+ "droplet",
2779
+ "droplet-half",
2524
2780
  "earth-americas",
2525
2781
  "earth-globe",
2526
2782
  "edit",
2783
+ "egg",
2527
2784
  "ellipsis-horizontal",
2528
2785
  "ellipsis-vertical",
2529
2786
  "empty",
@@ -2536,77 +2793,169 @@ var DOCK_ICON_NAMES = [
2536
2793
  "error-screen",
2537
2794
  "expand",
2538
2795
  "eye-closed",
2796
+ "eye-off",
2539
2797
  "eye-open",
2540
2798
  "face-happy",
2541
2799
  "face-indifferent",
2542
2800
  "face-sad",
2801
+ "feather",
2543
2802
  "feedback",
2803
+ "file",
2804
+ "file-certificate",
2805
+ "file-code",
2806
+ "file-download",
2807
+ "file-text",
2544
2808
  "filter",
2809
+ "flag",
2810
+ "flame",
2811
+ "flask",
2812
+ "focus-2",
2545
2813
  "folder",
2814
+ "folders",
2815
+ "forms",
2816
+ "frame",
2546
2817
  "generate",
2818
+ "gift",
2819
+ "git-branch",
2547
2820
  "github",
2821
+ "grid-dots",
2548
2822
  "groq",
2823
+ "hammer",
2824
+ "hand-finger",
2825
+ "hanger",
2549
2826
  "hash",
2827
+ "headphones",
2828
+ "headset",
2550
2829
  "heart",
2551
2830
  "heart-filled",
2831
+ "heart-handshake",
2552
2832
  "help-circle",
2553
2833
  "highlight",
2834
+ "history",
2554
2835
  "home",
2836
+ "hourglass",
2555
2837
  "ice-cream",
2838
+ "id-badge",
2839
+ "id-badge-2",
2556
2840
  "image",
2557
2841
  "image-remove",
2558
2842
  "images",
2559
2843
  "inbox",
2844
+ "infinity",
2845
+ "info-circle",
2560
2846
  "info-filled",
2561
2847
  "info-outline",
2848
+ "info-square-rounded",
2562
2849
  "inline",
2563
2850
  "inline-element",
2851
+ "input-search",
2564
2852
  "insert-above",
2565
2853
  "insert-below",
2566
2854
  "italic",
2567
2855
  "joystick",
2568
2856
  "json",
2857
+ "language",
2569
2858
  "launch",
2859
+ "layout",
2860
+ "layout-align-center",
2861
+ "layout-board",
2862
+ "layout-board-split",
2863
+ "layout-bottombar",
2864
+ "layout-bottombar-expand",
2865
+ "layout-cards",
2866
+ "layout-columns",
2867
+ "layout-dashboard",
2868
+ "layout-grid",
2869
+ "layout-list",
2870
+ "layout-navbar",
2871
+ "layout-rows",
2872
+ "layout-sidebar",
2873
+ "layout-sidebar-right",
2874
+ "leaf",
2570
2875
  "leave",
2571
2876
  "lemon",
2877
+ "library-photo",
2878
+ "lifebuoy",
2572
2879
  "link",
2573
2880
  "link-removed",
2574
2881
  "linkedin",
2575
2882
  "list",
2883
+ "list-check",
2884
+ "list-details",
2885
+ "list-numbers",
2886
+ "list-tree",
2576
2887
  "lock",
2577
2888
  "logo-js",
2578
2889
  "logo-ts",
2890
+ "mail",
2891
+ "map-2",
2892
+ "map-pin",
2893
+ "map-pins",
2579
2894
  "marker",
2580
2895
  "marker-removed",
2581
2896
  "master-detail",
2897
+ "math-function",
2898
+ "meat",
2582
2899
  "menu",
2900
+ "message",
2901
+ "message-2",
2902
+ "message-check",
2903
+ "message-circle",
2904
+ "message-circle-2",
2905
+ "message-plus",
2906
+ "message-star",
2583
2907
  "microphone",
2584
2908
  "microphone-slash",
2585
2909
  "mobile-device",
2910
+ "mood-empty",
2586
2911
  "moon",
2912
+ "movie",
2913
+ "news",
2914
+ "notes",
2587
2915
  "number",
2588
2916
  "ok-hand",
2589
2917
  "olist",
2590
2918
  "overage",
2591
2919
  "package",
2920
+ "packages",
2921
+ "palette",
2592
2922
  "panel-left",
2593
2923
  "panel-right",
2924
+ "paperclip",
2594
2925
  "pause",
2926
+ "pencil",
2927
+ "phone",
2928
+ "photo",
2929
+ "photo-scan",
2930
+ "photo-search",
2931
+ "pig-money",
2595
2932
  "pin",
2596
2933
  "pin-filled",
2597
2934
  "pin-removed",
2598
2935
  "play",
2936
+ "player-play",
2937
+ "player-track-next",
2938
+ "playlist-add",
2599
2939
  "plug",
2940
+ "plug-connected",
2941
+ "plus-minus",
2942
+ "point",
2600
2943
  "presentation",
2944
+ "printer",
2601
2945
  "progress-50",
2602
2946
  "progress-75",
2603
2947
  "projects",
2604
2948
  "publish",
2949
+ "qrcode",
2950
+ "quote",
2605
2951
  "read-only",
2952
+ "receipt",
2953
+ "recycle",
2606
2954
  "redo",
2607
2955
  "refresh",
2608
2956
  "remove",
2609
2957
  "remove-circle",
2958
+ "repeat",
2610
2959
  "reset",
2611
2960
  "restore",
2612
2961
  "retrieve",
@@ -2614,49 +2963,90 @@ var DOCK_ICON_NAMES = [
2614
2963
  "revert",
2615
2964
  "robot",
2616
2965
  "rocket",
2966
+ "rosette",
2967
+ "route",
2968
+ "ruler",
2969
+ "ruler-measure",
2970
+ "salad",
2971
+ "scale",
2617
2972
  "schema",
2973
+ "school",
2618
2974
  "search",
2975
+ "section",
2619
2976
  "select",
2977
+ "send",
2978
+ "server",
2979
+ "settings",
2620
2980
  "share",
2981
+ "shield-check",
2982
+ "shield-lock",
2983
+ "shopping-bag",
2984
+ "shopping-cart",
2985
+ "shopping-cart-plus",
2986
+ "signature",
2987
+ "sitemap",
2988
+ "slideshow",
2621
2989
  "sort",
2990
+ "sort-descending",
2991
+ "spacing-vertical",
2622
2992
  "sparkle",
2623
2993
  "sparkles",
2994
+ "speakerphone",
2624
2995
  "spinner",
2625
2996
  "split-horizontal",
2626
2997
  "split-vertical",
2627
2998
  "square",
2628
2999
  "stack",
3000
+ "stack-2",
2629
3001
  "stack-compact",
3002
+ "stairs",
3003
+ "stairs-up",
2630
3004
  "star",
2631
3005
  "star-filled",
3006
+ "stars",
2632
3007
  "stop",
2633
3008
  "strikethrough",
2634
3009
  "string",
3010
+ "subtask",
2635
3011
  "sun",
3012
+ "superscript",
2636
3013
  "sync",
3014
+ "table",
3015
+ "table-options",
2637
3016
  "tablet-device",
2638
3017
  "tag",
2639
3018
  "tags",
2640
3019
  "target",
2641
3020
  "task",
3021
+ "template",
3022
+ "tent",
2642
3023
  "terminal",
3024
+ "terminal-2",
2643
3025
  "text",
2644
3026
  "th-large",
2645
3027
  "th-list",
3028
+ "thumb-up",
2646
3029
  "thumbs-down",
2647
3030
  "thumbs-up",
3031
+ "ticket",
2648
3032
  "tiers",
2649
3033
  "timeline",
2650
3034
  "toggle-arrow-right",
3035
+ "toggle-right",
2651
3036
  "token",
3037
+ "tool",
3038
+ "tools-kitchen-2",
2652
3039
  "transfer",
2653
3040
  "translate",
2654
3041
  "trash",
2655
3042
  "trend-upward",
2656
3043
  "triangle-outline",
2657
3044
  "trolley",
3045
+ "truck",
3046
+ "truck-delivery",
2658
3047
  "truncate",
2659
3048
  "twitter",
3049
+ "typography",
2660
3050
  "ulist",
2661
3051
  "unarchive",
2662
3052
  "underline",
@@ -2667,12 +3057,22 @@ var DOCK_ICON_NAMES = [
2667
3057
  "unpublish",
2668
3058
  "upload",
2669
3059
  "user",
3060
+ "user-circle",
3061
+ "user-plus",
3062
+ "user-search",
3063
+ "user-star",
2670
3064
  "users",
3065
+ "users-group",
2671
3066
  "versions",
2672
3067
  "video",
3068
+ "vocabulary",
3069
+ "vs",
2673
3070
  "warning-filled",
2674
3071
  "warning-outline",
2675
- "wrench"
3072
+ "weight",
3073
+ "world",
3074
+ "wrench",
3075
+ "zoom-question"
2676
3076
  ];
2677
3077
  var DOCK_ICON_NAME_SET = new Set(DOCK_ICON_NAMES);
2678
3078
 
@@ -2683,12 +3083,12 @@ function listItems(f) {
2683
3083
  if (f.type === "repeater") return f.fields ?? [];
2684
3084
  if (f.type === "table") return c.fields ?? f.fields ?? [];
2685
3085
  if (f.type === "array" && c.zones?.repeatable) return c.zones.repeatable.fields ?? [];
2686
- if (f.type === "modular") return null;
3086
+ if (f.type === "modular" || f.type === "slot") return null;
2687
3087
  return void 0;
2688
3088
  }
2689
3089
  function zoneFields(f) {
2690
3090
  const c = f.config ?? {};
2691
- if (f.type === "group") return c.fields ?? f.fields ?? [];
3091
+ if (f.type === "group" || f.type === "object") return c.fields ?? f.fields ?? [];
2692
3092
  if (f.type === "array" && c.zones?.nonRepeatable) return c.zones.nonRepeatable;
2693
3093
  return void 0;
2694
3094
  }
@@ -2699,7 +3099,7 @@ function layout(fields, fieldsets, root) {
2699
3099
  const nonEmpty = new Set(
2700
3100
  fields.map((f) => tabOf(byId.get(f.fieldsetId ?? ""))).filter((t) => t && tabIds.includes(t))
2701
3101
  );
2702
- const strip = nonEmpty.size >= 2;
3102
+ const strip2 = nonEmpty.size >= 2;
2703
3103
  const branches = /* @__PURE__ */ new Map();
2704
3104
  const nodeFor = (f, path) => {
2705
3105
  const key = `${path}${f.key}`;
@@ -2720,12 +3120,12 @@ function layout(fields, fieldsets, root) {
2720
3120
  return { kind: "leaf", key };
2721
3121
  };
2722
3122
  const pinned = [];
2723
- const panels = new Map(strip ? tabIds.filter((t) => nonEmpty.has(t)).map((t) => [t, []]) : []);
3123
+ const panels = new Map(strip2 ? tabIds.filter((t) => nonEmpty.has(t)).map((t) => [t, []]) : []);
2724
3124
  const containers = /* @__PURE__ */ new Map();
2725
3125
  for (const f of fields) {
2726
3126
  const node = nodeFor(f, "");
2727
3127
  const set = byId.get(f.fieldsetId ?? "");
2728
- const target = strip && panels.get(tabOf(set) ?? "") || pinned;
3128
+ const target = strip2 && panels.get(tabOf(set) ?? "") || pinned;
2729
3129
  if (set?.kind !== "group" && set?.kind !== "row") {
2730
3130
  target.push(node);
2731
3131
  continue;
@@ -2927,12 +3327,101 @@ function buttonLinkPairs(candidates) {
2927
3327
 
2928
3328
  // src/button-look.ts
2929
3329
  import { ICON_MARKUP, iconSvg } from "@bettercms-ai/icons/markup";
3330
+
3331
+ // src/link-value.ts
3332
+ var LINK_RELS = ["nofollow", "sponsored", "ugc"];
3333
+ var LINK_VALUE_TYPES = ["url", "page", "entry", "email", "phone", "anchor", "section", "file"];
3334
+ var isPlainObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
3335
+ function isLinkObject(value) {
3336
+ return isPlainObject(value) && typeof value.href === "string";
3337
+ }
3338
+ function linkHref(value) {
3339
+ if (typeof value === "string") return value;
3340
+ return isLinkObject(value) ? value.href : null;
3341
+ }
3342
+ function linkRefOf(value) {
3343
+ if (!isLinkObject(value) || value.type !== "page" && value.type !== "entry") return null;
3344
+ const id = value.ref ?? (value.type === "page" ? value.pageId : value.entryId);
3345
+ return typeof id === "string" && id.length > 0 ? { kind: value.type, id } : null;
3346
+ }
3347
+ var isScriptHref = (href) => /^(javascript|data|vbscript):/i.test(href.replace(/[\u0000- \u007F]/g, ""));
3348
+ var newTabOf = (v) => v.newTab === true || v.newTab === void 0 && v.target === "_blank";
3349
+ function linkValueProblem(value) {
3350
+ if (!isPlainObject(value)) return "link_value_invalid: must be an href string or a link object { type, href, \u2026 }";
3351
+ const v = value;
3352
+ if (typeof v.href !== "string") return "link_value_invalid: needs an href string";
3353
+ if (isScriptHref(v.href)) return "link_value_invalid: script links (javascript:, data:, vbscript:) aren't allowed";
3354
+ if (v.type !== void 0 && !LINK_VALUE_TYPES.includes(v.type)) {
3355
+ return `link_value_invalid: unknown type "${String(v.type)}" (${LINK_VALUE_TYPES.join(", ")})`;
3356
+ }
3357
+ for (const key of ["title", "section", "ref"]) {
3358
+ if (v[key] !== void 0 && typeof v[key] !== "string") return `link_value_invalid: ${key} must be a string`;
3359
+ }
3360
+ 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";
3361
+ if (v.newTab !== void 0 && typeof v.newTab !== "boolean") return "link_value_invalid: newTab must be true or false";
3362
+ if (v.target !== void 0 && v.target !== "_self" && v.target !== "_blank") return 'link_value_invalid: target must be "_self" or "_blank"';
3363
+ if (typeof v.newTab === "boolean" && v.target !== void 0 && v.newTab !== (v.target === "_blank")) {
3364
+ return "link_value_invalid: newTab and target disagree";
3365
+ }
3366
+ if (v.rel !== void 0 && !LINK_RELS.includes(v.rel)) {
3367
+ return `link_value_invalid: unknown rel "${String(v.rel)}" (nofollow, sponsored or ugc)`;
3368
+ }
3369
+ return null;
3370
+ }
3371
+ function normalizeLinkValue(value) {
3372
+ if (!isLinkObject(value)) return value;
3373
+ const out = { ...value };
3374
+ delete out.resolved;
3375
+ if (out.rel === "follow") delete out.rel;
3376
+ if (out.newTab === void 0 && out.target !== void 0) out.newTab = out.target === "_blank";
3377
+ const ref = linkRefOf(out);
3378
+ if (ref && out.ref === void 0) out.ref = ref.id;
3379
+ return out;
3380
+ }
3381
+ function linkAnchorOf(value) {
3382
+ if (typeof value === "string") return { href: value, newTab: false };
3383
+ if (!isLinkObject(value)) return null;
3384
+ return {
3385
+ href: value.href,
3386
+ newTab: newTabOf(value),
3387
+ ...LINK_RELS.includes(value.rel) ? { rel: value.rel } : {}
3388
+ };
3389
+ }
3390
+
3391
+ // src/button-look.ts
2930
3392
  var BUTTON_VARIANTS = ["primary", "secondary", "tertiary", "brand", "dark", "light", "outline", "link"];
2931
3393
  var BUTTON_ICONS = ICON_MARKUP;
2932
3394
  var BUTTON_ICON_NAMES = DOCK_ICON_NAMES;
2933
3395
  function buttonVariantOf(value) {
2934
3396
  return BUTTON_VARIANTS.includes(value) ? value : "primary";
2935
3397
  }
3398
+ var SITE_CLASS = /^[^\s"'<>`=]{1,64}$/;
3399
+ var LOOK_VALUE = /^[a-z0-9][a-z0-9-]{0,63}$/;
3400
+ function buttonSiteLookOf(value) {
3401
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3402
+ const v = value;
3403
+ const tokens = (list) => Array.isArray(list) ? list.filter((c) => typeof c === "string" && SITE_CLASS.test(c)) : [];
3404
+ const variant = v.attrs?.["data-variant"];
3405
+ const classes = tokens(v.classes);
3406
+ const also = tokens(v.also);
3407
+ const size = tokens(v.size);
3408
+ const sizeAlso = tokens(v.sizeAlso);
3409
+ const attrs = typeof variant === "string" && LOOK_VALUE.test(variant) ? { "data-variant": variant } : void 0;
3410
+ if (classes.length === 0 && !attrs) return null;
3411
+ return {
3412
+ classes,
3413
+ ...attrs ? { attrs } : {},
3414
+ ...also.length ? { also } : {},
3415
+ ...size.length ? { size } : {},
3416
+ ...sizeAlso.length ? { sizeAlso } : {}
3417
+ };
3418
+ }
3419
+ function buttonLookValue(slug, siteLook) {
3420
+ return siteLook?.attrs?.["data-variant"] ?? slug;
3421
+ }
3422
+ function isButtonLookValue(value) {
3423
+ return typeof value === "string" && LOOK_VALUE.test(value);
3424
+ }
2936
3425
  function buttonIconOf(value) {
2937
3426
  return typeof value === "string" && DOCK_ICON_NAME_SET.has(value) ? value : null;
2938
3427
  }
@@ -2940,14 +3429,15 @@ function buttonIconSvg(name, cls) {
2940
3429
  return iconSvg(name, cls);
2941
3430
  }
2942
3431
  var BUTTON_SIZES = ["sm", "md", "lg"];
2943
- var LINK_RELS = ["nofollow", "sponsored", "ugc"];
2944
3432
  function anchorTargetRel(newTab, rel) {
2945
3433
  const tokens = [...newTab ? ["noopener", "noreferrer"] : [], ...LINK_RELS.includes(rel) ? [rel] : []];
2946
3434
  return `${newTab ? ' target="_blank"' : ""}${tokens.length ? ` rel="${tokens.join(" ")}"` : ""}`;
2947
3435
  }
2948
- var hrefOf = (href) => typeof href === "string" ? href : href && typeof href === "object" && typeof href.href === "string" ? href.href : null;
3436
+ var hrefOf = linkHref;
2949
3437
  function readButtonValue(value) {
2950
3438
  const v = value && typeof value === "object" ? value : {};
3439
+ const link = isLinkObject(v.href) ? linkAnchorOf(v.href) : null;
3440
+ const rel = v.rel ?? link?.rel;
2951
3441
  return {
2952
3442
  label: typeof v.label === "string" ? v.label : "",
2953
3443
  href: hrefOf(v.href) ?? "",
@@ -2955,16 +3445,22 @@ function readButtonValue(value) {
2955
3445
  size: BUTTON_SIZES.includes(v.size) ? v.size : "md",
2956
3446
  arrow: v.arrow === true,
2957
3447
  icon: buttonIconOf(v.icon),
2958
- newTab: v.newTab === true,
2959
- ...LINK_RELS.includes(v.rel) ? { rel: v.rel } : {}
3448
+ newTab: v.newTab === void 0 ? link?.newTab === true : v.newTab === true,
3449
+ ...LINK_RELS.includes(rel) ? { rel } : {}
2960
3450
  };
2961
3451
  }
2962
- function buttonValueProblem(value) {
3452
+ function buttonValueProblem(value, looks) {
2963
3453
  if (!value || typeof value !== "object" || Array.isArray(value)) return "must be a { label, href } button";
2964
3454
  const v = value;
2965
3455
  if (typeof v.label !== "string") return "needs a label";
2966
3456
  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)}"`;
3457
+ if (isLinkObject(v.href)) {
3458
+ const problem = linkValueProblem(v.href);
3459
+ if (problem) return `href ${problem}`;
3460
+ }
3461
+ if (v.variant !== void 0 && !BUTTON_VARIANTS.includes(v.variant) && !(typeof v.variant === "string" && looks?.has(v.variant))) {
3462
+ return `has an unknown variant "${String(v.variant)}"${looks?.size ? ` (this project's looks: ${[...looks].join(", ")})` : ""}`;
3463
+ }
2968
3464
  if (v.size !== void 0 && !BUTTON_SIZES.includes(v.size)) return `has an unknown size "${String(v.size)}"`;
2969
3465
  if (v.arrow !== void 0 && typeof v.arrow !== "boolean") return "arrow must be true or false";
2970
3466
  if (v.newTab !== void 0 && typeof v.newTab !== "boolean") return "newTab must be true or false";
@@ -2979,7 +3475,14 @@ function buttonBlockProps(value) {
2979
3475
  const out = {};
2980
3476
  if (typeof v.label === "string") out.text = v.label;
2981
3477
  const href = hrefOf(v.href);
2982
- if (href !== null) out.href = href;
3478
+ const script = href !== null && isScriptHref(href);
3479
+ if (href !== null) out.href = script ? "#" : href;
3480
+ if (isLinkObject(v.href) && !script) {
3481
+ const link = linkAnchorOf(v.href);
3482
+ out.newTab = link.newTab;
3483
+ out.rel = link.rel;
3484
+ }
3485
+ if (v.rel !== void 0) out.rel = v.rel;
2983
3486
  for (const key of BUTTON_LOOK_KEYS) if (key in v) out[key] = v[key];
2984
3487
  return out;
2985
3488
  }
@@ -3032,6 +3535,537 @@ function buttonValueOfInstance(def, overrides) {
3032
3535
  return readButtonValue({ ...props, label: props.text });
3033
3536
  }
3034
3537
 
3538
+ // src/video-value.ts
3539
+ var VIDEO_VALUE_KEYS = ["url", "poster", "autoplay", "loop", "transcript"];
3540
+ var VIDEO_TRANSCRIPT_MAX = 1e5;
3541
+ var isPlainObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
3542
+ var isVideoUrl = (s) => s === "" || /^https?:\/\/[^\s]+$/i.test(s.trim()) || /^\/(?!\/)\S*$/.test(s.trim());
3543
+ function videoUrlOf(value) {
3544
+ const url = typeof value === "string" ? value : isPlainObject2(value) ? value.url : void 0;
3545
+ return typeof url === "string" && url.trim() !== "" ? url.trim() : null;
3546
+ }
3547
+ function videoValueProblem(value) {
3548
+ if (typeof value === "string") return isVideoUrl(value) ? null : "video_value_invalid: url must be an http(s) URL or a site path";
3549
+ if (!isPlainObject2(value)) return "video_value_invalid: must be a URL string or { url, poster?, autoplay?, loop?, transcript? }";
3550
+ const unknown = Object.keys(value).find((k) => !VIDEO_VALUE_KEYS.includes(k));
3551
+ if (unknown !== void 0) return `video_value_invalid: unknown key "${unknown}" (${VIDEO_VALUE_KEYS.join(", ")})`;
3552
+ if (typeof value.url !== "string") return "video_value_invalid: needs a url string";
3553
+ if (!isVideoUrl(value.url)) return "video_value_invalid: url must be an http(s) URL or a site path";
3554
+ if (value.poster !== void 0 && (typeof value.poster !== "string" || !isVideoUrl(value.poster))) {
3555
+ return "video_value_invalid: poster must be an http(s) URL or a site path";
3556
+ }
3557
+ for (const key of ["autoplay", "loop"]) {
3558
+ if (value[key] !== void 0 && typeof value[key] !== "boolean") return `video_value_invalid: ${key} must be true or false`;
3559
+ }
3560
+ if (value.transcript !== void 0 && (typeof value.transcript !== "string" || value.transcript.length > VIDEO_TRANSCRIPT_MAX)) {
3561
+ return `video_value_invalid: transcript must be a string of ${VIDEO_TRANSCRIPT_MAX} characters or fewer`;
3562
+ }
3563
+ return null;
3564
+ }
3565
+
3566
+ // src/table-value.ts
3567
+ var TABLE_COLUMN_TYPES = ["text", "number", "money", "boolean", "rating", "link", "richtext-inline", "reference"];
3568
+ var TABLE_RESPONSIVE = ["scroll", "stack", "cards"];
3569
+ var TABLE_JSONLD_TYPES = ["none", "ItemList", "Dataset", "Offers"];
3570
+ var TABLE_LIMITS = { columns: 50, rows: 1e3, cellChars: 5e3, captionChars: 300, notesChars: 2e3, keyChars: 64, labelChars: 300 };
3571
+ var isObj = (v) => !!v && typeof v === "object" && !Array.isArray(v);
3572
+ var isTableValue = (v) => isObj(v) && Array.isArray(v.columns) && Array.isArray(v.rows);
3573
+ var isTableCellObject = (c) => isObj(c) && "value" in c;
3574
+ var isTableRef = (v) => isObj(v) && "ref" in v;
3575
+ var isTableLink = (v) => isObj(v) && !("ref" in v) && "href" in v;
3576
+ var tableCellValue = (c) => c === void 0 ? null : isTableCellObject(c) ? c.value : c;
3577
+ var tableCellType = (col, row, c) => isTableCellObject(c) && c.type || row?.type || col.type;
3578
+ var TOP_KEYS = /* @__PURE__ */ new Set(["id", "caption", "notes", "headerRow", "headerColumn", "columns", "columnGroups", "rowGroups", "rows", "footnotes", "filterable", "design", "jsonLd"]);
3579
+ var COLUMN_KEYS = /* @__PURE__ */ new Set(["_key", "label", "type", "description", "unit", "currency", "decimals", "align", "sortable", "group", "trueLabel", "falseLabel", "max", "refTo", "highlight"]);
3580
+ var ROW_KEYS = /* @__PURE__ */ new Set(["_key", "cells", "group", "footer", "type", "highlight"]);
3581
+ var GROUP_KEYS = /* @__PURE__ */ new Set(["_key", "label"]);
3582
+ var FOOTNOTE_KEYS = /* @__PURE__ */ new Set(["_key", "text"]);
3583
+ var DESIGN_KEYS = /* @__PURE__ */ new Set(["responsive", "sticky", "striped", "density", "hideCaption"]);
3584
+ var CELL_KEYS = /* @__PURE__ */ new Set(["value", "type", "note"]);
3585
+ var LINK_KEYS = /* @__PURE__ */ new Set(["label", "href", "newTab", "rel"]);
3586
+ var REF_KEYS = /* @__PURE__ */ new Set(["ref", "label", "href", "resolved"]);
3587
+ var TABLE_LINK_RELS = ["follow", ...LINK_RELS];
3588
+ function tableValueProblems(value, opts = {}) {
3589
+ const out = [];
3590
+ const bad = (path, message, code = "table_value_invalid") => out.push({ path, code, message: `${code}: ${message}` });
3591
+ if (!isTableValue(value)) {
3592
+ bad([], "a table is { caption, headerRow, headerColumn, columns: [{ _key, label, type }], rows: [{ _key, cells }] }");
3593
+ return out;
3594
+ }
3595
+ const t = value;
3596
+ const unknownKeys = (o, allowed, at) => {
3597
+ for (const k of Object.keys(o)) if (!allowed.has(k)) bad([...at, k], `unknown key "${k}" (${[...allowed].join(", ")})`);
3598
+ };
3599
+ const str = (v, at, what, max, required = false) => {
3600
+ if (v === void 0 && !required) return;
3601
+ if (typeof v !== "string") bad(at, `${what} must be a string`);
3602
+ else if (v.length > max) bad(at, `${what} is ${v.length} characters; at most ${max}`, "table_limit_exceeded");
3603
+ };
3604
+ const bool = (v, at, what) => {
3605
+ if (v !== void 0 && typeof v !== "boolean") bad(at, `${what} must be true or false`);
3606
+ };
3607
+ const oneOf = (v, at, what, allowed) => {
3608
+ if (v !== void 0 && !allowed.includes(v)) bad(at, `${what} must be one of ${allowed.join(", ")}`);
3609
+ };
3610
+ unknownKeys(t, TOP_KEYS, []);
3611
+ str(t.id, ["id"], "id", TABLE_LIMITS.keyChars);
3612
+ str(t.caption, ["caption"], "caption", TABLE_LIMITS.captionChars, true);
3613
+ if (typeof t.caption === "string" && t.caption.trim() === "" && opts.requireCaption !== false) {
3614
+ bad(["caption"], "give the table a caption that says what it compares (it names the table for screen readers and search)", "table_caption_required");
3615
+ }
3616
+ str(t.notes, ["notes"], "notes", TABLE_LIMITS.notesChars);
3617
+ if (t.notes !== void 0 && opts.allowNote === false && typeof t.notes === "string" && t.notes.trim() !== "") bad(["notes"], "this table takes no note (config.allowNote is false)");
3618
+ bool(t.headerRow, ["headerRow"], "headerRow");
3619
+ bool(t.headerColumn, ["headerColumn"], "headerColumn");
3620
+ bool(t.filterable, ["filterable"], "filterable");
3621
+ if (t.design !== void 0) {
3622
+ if (!isObj(t.design)) bad(["design"], "design must be an object");
3623
+ else {
3624
+ const d = t.design;
3625
+ unknownKeys(d, DESIGN_KEYS, ["design"]);
3626
+ oneOf(d.responsive, ["design", "responsive"], "design.responsive", TABLE_RESPONSIVE);
3627
+ oneOf(d.density, ["design", "density"], "design.density", ["compact", "comfortable"]);
3628
+ for (const k of ["sticky", "striped", "hideCaption"]) bool(d[k], ["design", k], `design.${k}`);
3629
+ }
3630
+ }
3631
+ const keyed = (list, at, keys, textKey, allowed) => {
3632
+ const seen = /* @__PURE__ */ new Set();
3633
+ if (list === void 0) return seen;
3634
+ if (!Array.isArray(list)) {
3635
+ bad([at], `${at} must be an array`);
3636
+ return seen;
3637
+ }
3638
+ if (!allowed && list.length > 0) bad([at], `this table takes no ${at === "footnotes" ? "footnotes (config.allowFootnotes is false)" : "row groups (config.allowGroups is false)"}`);
3639
+ list.forEach((g, i) => {
3640
+ if (!isObj(g)) return bad([at, i], `${at}[${i}] must be an object`);
3641
+ unknownKeys(g, keys, [at, i]);
3642
+ if (typeof g._key !== "string" || g._key === "" || g._key.length > TABLE_LIMITS.keyChars) bad([at, i, "_key"], `needs a _key string of 1 to ${TABLE_LIMITS.keyChars} characters`);
3643
+ else if (seen.has(g._key)) bad([at, i, "_key"], `_key "${g._key}" is used twice`);
3644
+ else seen.add(g._key);
3645
+ str(g[textKey], [at, i, textKey], textKey, textKey === "text" ? TABLE_LIMITS.notesChars : TABLE_LIMITS.labelChars, true);
3646
+ });
3647
+ return seen;
3648
+ };
3649
+ const colGroups = keyed(t.columnGroups, "columnGroups", GROUP_KEYS, "label", true);
3650
+ const rowGroups = keyed(t.rowGroups, "rowGroups", GROUP_KEYS, "label", opts.allowGroups !== false);
3651
+ const notes = keyed(t.footnotes, "footnotes", FOOTNOTE_KEYS, "text", opts.allowFootnotes !== false);
3652
+ const columns = t.columns;
3653
+ const maxColumns = Math.min(opts.maxColumns ?? TABLE_LIMITS.columns, TABLE_LIMITS.columns);
3654
+ if (columns.length > maxColumns) bad(["columns"], `${columns.length} columns; at most ${maxColumns}`, "table_limit_exceeded");
3655
+ const cols = /* @__PURE__ */ new Map();
3656
+ columns.forEach((c, i) => {
3657
+ const at = ["columns", i];
3658
+ if (!isObj(c)) return bad(at, `columns[${i}] must be an object`);
3659
+ unknownKeys(c, COLUMN_KEYS, at);
3660
+ if (typeof c._key !== "string" || c._key === "" || c._key.length > TABLE_LIMITS.keyChars) bad([...at, "_key"], `needs a _key string of 1 to ${TABLE_LIMITS.keyChars} characters`);
3661
+ else if (cols.has(c._key)) bad([...at, "_key"], `_key "${c._key}" is used twice`);
3662
+ str(c.label, [...at, "label"], "label", TABLE_LIMITS.labelChars, true);
3663
+ if (!TABLE_COLUMN_TYPES.includes(c.type)) bad([...at, "type"], `type must be one of ${TABLE_COLUMN_TYPES.join(", ")}`);
3664
+ else if (opts.columnTypes && !opts.columnTypes.includes(c.type)) {
3665
+ bad([...at, "type"], `a ${String(c.type)} column is not offered here (config.columnTypes: ${opts.columnTypes.join(", ")})`, "table_column_type_not_allowed");
3666
+ }
3667
+ str(c.description, [...at, "description"], "description", TABLE_LIMITS.notesChars);
3668
+ str(c.unit, [...at, "unit"], "unit", 16);
3669
+ if (c.currency !== void 0 && (typeof c.currency !== "string" || !/^[A-Z]{3}$/.test(c.currency))) bad([...at, "currency"], "currency must be an ISO 4217 code such as USD");
3670
+ if (c.decimals !== void 0 && (!Number.isInteger(c.decimals) || c.decimals < 0 || c.decimals > 10)) bad([...at, "decimals"], "decimals must be a whole number from 0 to 10");
3671
+ if (c.max !== void 0 && (!Number.isInteger(c.max) || c.max < 1 || c.max > 10)) bad([...at, "max"], "max must be a whole number from 1 to 10");
3672
+ oneOf(c.align, [...at, "align"], "align", ["start", "center", "end"]);
3673
+ for (const k of ["sortable", "highlight"]) bool(c[k], [...at, k], k);
3674
+ str(c.trueLabel, [...at, "trueLabel"], "trueLabel", TABLE_LIMITS.labelChars);
3675
+ str(c.falseLabel, [...at, "falseLabel"], "falseLabel", TABLE_LIMITS.labelChars);
3676
+ if (c.group !== void 0 && (typeof c.group !== "string" || !colGroups.has(c.group))) bad([...at, "group"], `group "${String(c.group)}" is not a key of columnGroups`);
3677
+ if (c.refTo !== void 0 && (!Array.isArray(c.refTo) || c.refTo.some((r) => typeof r !== "string" || r === ""))) bad([...at, "refTo"], 'refTo must be a list of collection ids or "page"');
3678
+ if (typeof c._key === "string" && c._key !== "" && !cols.has(c._key)) cols.set(c._key, c);
3679
+ });
3680
+ if (opts.lockColumns && opts.lockedColumns) {
3681
+ const want = opts.lockedColumns.map((c) => `${c._key}:${c.type}`).join(",");
3682
+ const got = columns.map((c) => isObj(c) ? `${String(c._key)}:${String(c.type)}` : "?").join(",");
3683
+ if (want !== got) bad(["columns"], `this table's columns are set by its developer (config.lockColumns): keep ${opts.lockedColumns.map((c) => c._key).join(", ")}, in that order, with their types`, "table_columns_locked");
3684
+ }
3685
+ const rows = t.rows;
3686
+ const maxRows = Math.min(opts.maxRows ?? TABLE_LIMITS.rows, TABLE_LIMITS.rows);
3687
+ const body = rows.filter((r) => !(isObj(r) && r.footer === true)).length;
3688
+ if (rows.length > TABLE_LIMITS.rows || body > maxRows) bad(["rows"], `${body} rows; at most ${maxRows}`, "table_limit_exceeded");
3689
+ if (opts.minRows !== void 0 && body < opts.minRows) bad(["rows"], `${body} rows; at least ${opts.minRows}`, "table_rows_min");
3690
+ const rowKeys = /* @__PURE__ */ new Set();
3691
+ rows.forEach((r, i) => {
3692
+ const at = ["rows", i];
3693
+ if (!isObj(r)) return bad(at, `rows[${i}] must be an object`);
3694
+ unknownKeys(r, ROW_KEYS, at);
3695
+ if (typeof r._key !== "string" || r._key === "" || r._key.length > TABLE_LIMITS.keyChars) bad([...at, "_key"], `needs a _key string of 1 to ${TABLE_LIMITS.keyChars} characters`);
3696
+ else if (rowKeys.has(r._key)) bad([...at, "_key"], `_key "${r._key}" is used twice`);
3697
+ else rowKeys.add(r._key);
3698
+ if (r.group !== void 0 && (typeof r.group !== "string" || !rowGroups.has(r.group))) bad([...at, "group"], `group "${String(r.group)}" is not a key of rowGroups`);
3699
+ bool(r.footer, [...at, "footer"], "footer");
3700
+ bool(r.highlight, [...at, "highlight"], "highlight");
3701
+ oneOf(r.type, [...at, "type"], "type", TABLE_COLUMN_TYPES);
3702
+ if (!isObj(r.cells)) return bad([...at, "cells"], "cells must be an object keyed by column _key");
3703
+ for (const [k, cell] of Object.entries(r.cells)) {
3704
+ const cAt = [...at, "cells", k];
3705
+ const col = cols.get(k);
3706
+ if (!col) {
3707
+ bad(cAt, `no column has _key "${k}"`);
3708
+ continue;
3709
+ }
3710
+ cellProblems(cell, col, r, cAt, notes, opts, bad);
3711
+ }
3712
+ });
3713
+ return out;
3714
+ }
3715
+ function cellProblems(cell, col, row, at, notes, opts, bad) {
3716
+ if (cell === void 0 || cell === null) return;
3717
+ let v = cell;
3718
+ let vAt = at;
3719
+ if (isTableCellObject(cell)) {
3720
+ for (const k of Object.keys(cell)) if (!CELL_KEYS.has(k)) bad([...at, k], `unknown key "${k}" (value, type, note)`);
3721
+ if (cell.type !== void 0 && !TABLE_COLUMN_TYPES.includes(cell.type)) bad([...at, "type"], `type must be one of ${TABLE_COLUMN_TYPES.join(", ")}`);
3722
+ if (cell.note !== void 0 && (typeof cell.note !== "string" || !notes.has(cell.note))) bad([...at, "note"], `note "${String(cell.note)}" is not a key of footnotes`);
3723
+ v = cell.value;
3724
+ vAt = [...at, "value"];
3725
+ if (v === null) return;
3726
+ }
3727
+ const type = tableCellType(col, row, cell);
3728
+ if (typeof v === "string") {
3729
+ if (v.length > TABLE_LIMITS.cellChars) bad(vAt, `a cell holds at most ${TABLE_LIMITS.cellChars} characters`, "table_limit_exceeded");
3730
+ return;
3731
+ }
3732
+ if (typeof v === "number") {
3733
+ if (!Number.isFinite(v)) bad(vAt, "a number cell must be finite");
3734
+ return;
3735
+ }
3736
+ if (typeof v === "boolean") return;
3737
+ if (!isObj(v)) return bad(vAt, "a cell holds text, a number, true / false, a link { label, href } or a reference { ref }");
3738
+ if ("ref" in v) {
3739
+ if (type !== "reference") return bad(vAt, `a reference { ref } belongs in a reference cell; this cell is ${type}`);
3740
+ for (const k of Object.keys(v)) if (!REF_KEYS.has(k)) bad([...vAt, k], `unknown key "${k}" (ref, label, href)`);
3741
+ if (typeof v.ref !== "string" || v.ref === "") bad([...vAt, "ref"], "ref must be the id of a page or an entry");
3742
+ if (v.label !== void 0 && typeof v.label !== "string") bad([...vAt, "label"], "label must be a string");
3743
+ if (v.href !== void 0 && (typeof v.href !== "string" || isScriptHref(v.href))) bad([...vAt, "href"], "href must be a link the site can hold (no script links)", "link_value_invalid");
3744
+ return;
3745
+ }
3746
+ if (type !== "link") return bad(vAt, `a link { label, href } belongs in a link cell; this cell is ${type}`);
3747
+ for (const k of Object.keys(v)) if (!LINK_KEYS.has(k)) bad([...vAt, k], `unknown key "${k}" (label, href, newTab, rel)`);
3748
+ if (typeof v.label !== "string" && v.label !== void 0) bad([...vAt, "label"], "label must be a string");
3749
+ if (typeof v.href !== "string") return bad([...vAt, "href"], "needs an href string", "link_value_invalid");
3750
+ if (isScriptHref(v.href)) return bad([...vAt, "href"], "script links (javascript:, data:, vbscript:) aren't allowed", "link_value_invalid");
3751
+ const grammar = v.href !== "" ? opts.hrefProblem?.(v.href) : null;
3752
+ if (grammar) bad([...vAt, "href"], grammar, "link_value_invalid");
3753
+ if (v.newTab !== void 0 && typeof v.newTab !== "boolean") bad([...vAt, "newTab"], "newTab must be true or false", "link_value_invalid");
3754
+ if (v.rel !== void 0 && !TABLE_LINK_RELS.includes(v.rel)) bad([...vAt, "rel"], `rel must be one of ${TABLE_LINK_RELS.join(", ")}`, "link_value_invalid");
3755
+ }
3756
+ function tablePathString(path) {
3757
+ return path.map((s, i) => typeof s === "number" ? `[${s}]` : i === 0 ? s : `.${s}`).join("");
3758
+ }
3759
+ function normalizeTableValue(value) {
3760
+ if (!isTableValue(value)) return value;
3761
+ const t = structuredClone(value);
3762
+ t.headerRow = t.headerRow !== false;
3763
+ t.headerColumn = t.headerColumn === true;
3764
+ for (const r of t.rows) {
3765
+ if (!isObj(r) || !isObj(r.cells)) continue;
3766
+ for (const c of Object.values(r.cells)) {
3767
+ const v = isTableCellObject(c) ? c.value : c;
3768
+ if (isTableRef(v)) delete v.resolved;
3769
+ }
3770
+ }
3771
+ return t;
3772
+ }
3773
+ var readTable = (v) => ({ ...v, headerRow: v.headerRow !== false, headerColumn: v.headerColumn === true });
3774
+ function tableRefCells(value) {
3775
+ if (!isTableValue(value)) return [];
3776
+ const cols = new Map(value.columns.filter(isObj).map((c) => [c._key, c]));
3777
+ const out = [];
3778
+ value.rows.forEach((r, i) => {
3779
+ if (!isObj(r) || !isObj(r.cells)) return;
3780
+ for (const [k, c] of Object.entries(r.cells)) {
3781
+ const col = cols.get(k);
3782
+ if (!col) continue;
3783
+ const v = isTableCellObject(c) ? c.value : c;
3784
+ if (!isTableRef(v) || typeof v.ref !== "string" || v.ref === "" || tableCellType(col, r, c) !== "reference") continue;
3785
+ out.push({ cell: v, id: v.ref, path: ["rows", i, "cells", k, ...isTableCellObject(c) ? ["value"] : []], refTo: Array.isArray(col.refTo) && col.refTo.length ? col.refTo : null });
3786
+ }
3787
+ });
3788
+ return out;
3789
+ }
3790
+ var strip = (s) => s.replace(/<[^>]*>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
3791
+ var TRUE_WORDS = /* @__PURE__ */ new Set(["\u2713", "\u2714", "\u2714\uFE0F", "\u2611", "\u2705", "yes", "y", "true", "included", "available", "supported", "check", "\u25CF"]);
3792
+ var FALSE_WORDS = /* @__PURE__ */ new Set(["\u2717", "\u2715", "\u2716", "\u274C", "\xD7", "x", "no", "n", "false", "not included", "unavailable", "not available", "unsupported", "\u2014", "\u2013", "-", "\u25CB"]);
3793
+ function toBool(v) {
3794
+ if (typeof v === "boolean") return v;
3795
+ if (typeof v !== "string") return null;
3796
+ const s = strip(v).trim().toLowerCase();
3797
+ return TRUE_WORDS.has(s) ? true : FALSE_WORDS.has(s) ? false : null;
3798
+ }
3799
+ function toNumber(v) {
3800
+ if (typeof v === "number") return Number.isFinite(v) ? v : null;
3801
+ if (typeof v !== "string") return null;
3802
+ const s = v.replace(/[,\s]/g, "").replace(/[^\d.+-]/g, "");
3803
+ return s && /^[+-]?\d*\.?\d+$/.test(s) ? Number(s) : null;
3804
+ }
3805
+ var formatNumber2 = (n, decimals) => new Intl.NumberFormat("en-US", decimals != null ? { minimumFractionDigits: decimals, maximumFractionDigits: decimals } : { maximumFractionDigits: 6 }).format(n);
3806
+ function formatMoney(n, currency = "USD", decimals) {
3807
+ try {
3808
+ const whole = Number.isInteger(n) && decimals == null;
3809
+ return new Intl.NumberFormat("en-US", { style: "currency", currency, minimumFractionDigits: whole ? 0 : decimals ?? 2, maximumFractionDigits: whole ? 0 : decimals ?? 2 }).format(n);
3810
+ } catch {
3811
+ return `${formatNumber2(n, decimals)} ${currency}`;
3812
+ }
3813
+ }
3814
+ var isEmptyCell = (v) => v == null || v === "" || isObj(v) && !(isTableRef(v) ? v.ref : v.label || v.href);
3815
+ var refLabel = (r) => r.resolved?.title || r.label || r.ref;
3816
+ var refHref = (r) => r.resolved?.url ?? r.href;
3817
+ function tableCellText(col, row, c) {
3818
+ const v = tableCellValue(c);
3819
+ if (isEmptyCell(v)) return "";
3820
+ switch (tableCellType(col, row, c)) {
3821
+ case "boolean": {
3822
+ const b = toBool(v);
3823
+ return b == null ? strip(String(v)) : b ? col.trueLabel || "Included" : col.falseLabel || "Not included";
3824
+ }
3825
+ case "number": {
3826
+ const n = toNumber(v);
3827
+ return n == null ? strip(String(v)) : `${formatNumber2(n, col.decimals)}${col.unit ? /^[%°]/.test(col.unit) ? col.unit : ` ${col.unit}` : ""}`;
3828
+ }
3829
+ case "money": {
3830
+ const n = toNumber(v);
3831
+ return n == null ? strip(String(v)) : formatMoney(n, col.currency || "USD", col.decimals);
3832
+ }
3833
+ case "rating": {
3834
+ const n = toNumber(v);
3835
+ return n == null ? strip(String(v)) : `${formatNumber2(n, 1).replace(/\.0$/, "")} out of ${col.max ?? 5}`;
3836
+ }
3837
+ case "link":
3838
+ return isObj(v) ? String(v.label || v.href || "") : strip(String(v));
3839
+ case "reference":
3840
+ return isTableRef(v) ? refLabel(v) : strip(String(v));
3841
+ default:
3842
+ return isObj(v) ? String(v.label ?? "") : strip(String(v)).replace(/\s+/g, " ").trim();
3843
+ }
3844
+ }
3845
+ var esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3846
+ var escAttr = (s) => esc(s).replace(/"/g, "&quot;");
3847
+ var safeHref = (h) => isScriptHref(h) ? "#" : h;
3848
+ var INLINE_TAGS = /* @__PURE__ */ new Set(["strong", "b", "em", "i", "u", "s", "del", "code", "a", "br", "sup", "sub", "mark", "small", "abbr"]);
3849
+ function sanitizeInline(html) {
3850
+ return html.replace(/<(script|style|iframe|object|embed|template)\b[\s\S]*?<\/\1>/gi, "").replace(/<\/?([a-z][a-z0-9]*)\b([^>]*)>/gi, (m, tag, attrs) => {
3851
+ const t = tag.toLowerCase();
3852
+ if (!INLINE_TAGS.has(t)) return "";
3853
+ const name = t === "b" ? "strong" : t === "i" ? "em" : t;
3854
+ if (m.startsWith("</")) return `</${name}>`;
3855
+ if (t === "a") {
3856
+ const href = attrs.match(/href\s*=\s*("([^"]*)"|'([^']*)')/i);
3857
+ return `<a href="${escAttr(safeHref(href ? href[2] ?? href[3] ?? "" : ""))}">`;
3858
+ }
3859
+ if (t === "abbr") {
3860
+ const title = attrs.match(/title\s*=\s*"([^"]*)"/i);
3861
+ return title ? `<abbr title="${escAttr(title[1])}">` : "<abbr>";
3862
+ }
3863
+ return `<${name}>`;
3864
+ });
3865
+ }
3866
+ var alignOf = (col, t) => col.align ?? (t === "number" || t === "money" || t === "rating" ? "end" : "start");
3867
+ function orderedRows(t) {
3868
+ const groups = (t.rowGroups ?? []).filter(isObj);
3869
+ const known = new Set(groups.map((g) => g._key));
3870
+ const body = t.rows.filter((r) => isObj(r) && !r.footer);
3871
+ const loose = body.filter((r) => !r.group || !known.has(r.group));
3872
+ const bodies = [];
3873
+ if (loose.length || !groups.length) bodies.push({ rows: loose });
3874
+ for (const g of groups) {
3875
+ const rows = body.filter((r) => r.group === g._key);
3876
+ if (rows.length) bodies.push({ group: g, rows });
3877
+ }
3878
+ return { bodies, foot: t.rows.filter((r) => isObj(r) && r.footer) };
3879
+ }
3880
+ function cellHtml(t, col, row, c, fn, asHeader) {
3881
+ const v = tableCellValue(c);
3882
+ const type = tableCellType(col, row, c);
3883
+ const note = isTableCellObject(c) ? c.note : void 0;
3884
+ const n = note ? fn.get(note) : void 0;
3885
+ const sup = n ? `<sup><a href="#${escAttr(t.id ?? "tbl")}-fn-${n}" aria-label="Note ${n}">${n}</a></sup>` : "";
3886
+ let inner;
3887
+ if (isEmptyCell(v)) inner = asHeader ? "" : `<span aria-hidden="true" class="bt-dash">\u2014</span><span class="bt-sr">No value</span>`;
3888
+ else
3889
+ switch (type) {
3890
+ case "boolean": {
3891
+ const b = toBool(v);
3892
+ inner = b == null ? esc(strip(String(v))) : `<span aria-hidden="true" class="bt-bool" data-bool="${b}">${b ? "\u2713" : "\u2013"}</span><span class="bt-sr">${esc(b ? col.trueLabel || "Included" : col.falseLabel || "Not included")}</span>`;
3893
+ break;
3894
+ }
3895
+ case "rating": {
3896
+ const num = toNumber(v);
3897
+ inner = num == null ? esc(strip(String(v))) : `<span aria-hidden="true" class="bt-star">\u2605</span> ${esc(formatNumber2(num, 1).replace(/\.0$/, ""))}<span class="bt-dim"> / ${col.max ?? 5}</span>`;
3898
+ break;
3899
+ }
3900
+ case "link": {
3901
+ const l = isObj(v) ? v : { label: String(v), href: "" };
3902
+ const rel = [l.newTab ? "noopener noreferrer" : "", l.rel && l.rel !== "follow" ? l.rel : ""].filter(Boolean).join(" ");
3903
+ inner = l.href ? `<a href="${escAttr(safeHref(l.href))}"${l.newTab ? ' target="_blank"' : ""}${rel ? ` rel="${rel}"` : ""}>${esc(l.label || l.href)}</a>` : esc(l.label ?? "");
3904
+ break;
3905
+ }
3906
+ case "reference": {
3907
+ if (!isTableRef(v)) {
3908
+ inner = esc(strip(String(v)));
3909
+ break;
3910
+ }
3911
+ const href = refHref(v);
3912
+ inner = href ? `<a href="${escAttr(safeHref(href))}">${esc(refLabel(v))}</a>` : esc(refLabel(v));
3913
+ break;
3914
+ }
3915
+ case "richtext-inline":
3916
+ inner = sanitizeInline(String(isObj(v) ? v.label ?? "" : v));
3917
+ break;
3918
+ default:
3919
+ inner = esc(type === "number" || type === "money" ? tableCellText(col, row, c) : isObj(v) ? String(v.label ?? "") : strip(String(v)));
3920
+ }
3921
+ return { inner: inner + sup, align: alignOf(col, type) };
3922
+ }
3923
+ var TABLE_CSS = '.bt-table .bt-sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.bt-table .bt-scroll{overflow-x:auto}.bt-table table{border-collapse:collapse;width:100%}.bt-table th,.bt-table td{padding:8px 12px;border-bottom:1px solid #e5e5e5;text-align:start;vertical-align:top}.bt-table [data-align="end"]{text-align:end}.bt-table [data-align="center"]{text-align:center}.bt-table caption{text-align:start;font-weight:600;padding-bottom:8px}.bt-table .bt-notes{font-size:.9em;margin-top:8px}';
3924
+ function renderTableHtml(input) {
3925
+ const t = readTable(input);
3926
+ const id = (t.id ?? "tbl").replace(/[^\w-]/g, "") || "tbl";
3927
+ const cols = t.columns.filter(isObj);
3928
+ const n = cols.length;
3929
+ if (!n) return "";
3930
+ const fn = new Map((t.footnotes ?? []).filter(isObj).map((f, i) => [f._key, i + 1]));
3931
+ const groups = (t.columnGroups ?? []).filter((g) => isObj(g) && cols.some((c) => c.group === g._key));
3932
+ const hasNotes = !!t.notes || fn.size > 0;
3933
+ const responsive = t.design?.responsive ?? "scroll";
3934
+ const fig = [];
3935
+ const attrs = [
3936
+ `class="bt-table"`,
3937
+ `data-responsive="${escAttr(responsive)}"`,
3938
+ t.design?.sticky ? `data-sticky="true"` : "",
3939
+ t.design?.striped ? `data-striped="true"` : "",
3940
+ t.design?.density === "compact" ? `data-density="compact"` : ""
3941
+ ].filter(Boolean);
3942
+ fig.push(`<figure ${attrs.join(" ")}><style>${TABLE_CSS}</style>`);
3943
+ fig.push(`<div class="bt-scroll" role="region" tabindex="0" aria-labelledby="${id}-caption">`);
3944
+ fig.push(`<table${hasNotes ? ` aria-describedby="${id}-notes"` : ""}>`);
3945
+ fig.push(`<caption id="${id}-caption"${t.design?.hideCaption ? ` class="bt-sr"` : ""}>${esc(t.caption || "Table")}</caption>`);
3946
+ if (t.headerRow) {
3947
+ fig.push("<thead>");
3948
+ if (groups.length) {
3949
+ const cells = [];
3950
+ for (let i = 0; i < n; ) {
3951
+ const g = cols[i].group ? groups.find((x) => x._key === cols[i].group) : void 0;
3952
+ if (!g) {
3953
+ cells.push("<td></td>");
3954
+ i++;
3955
+ continue;
3956
+ }
3957
+ let span = 1;
3958
+ while (i + span < n && cols[i + span].group === g._key) span++;
3959
+ cells.push(`<th scope="colgroup"${span > 1 ? ` colspan="${span}"` : ""}>${esc(g.label)}</th>`);
3960
+ i += span;
3961
+ }
3962
+ fig.push(`<tr>${cells.join("")}</tr>`);
3963
+ }
3964
+ fig.push(
3965
+ `<tr>${cols.map((c, i) => {
3966
+ if (i === 0 && t.headerColumn && !String(c.label ?? "").trim()) return "<td></td>";
3967
+ const align = alignOf(c, c.type);
3968
+ return `<th scope="col"${align !== "start" ? ` data-align="${align}"` : ""}${c.highlight ? ` data-highlight="true"` : ""}${c.description ? ` title="${escAttr(c.description)}"` : ""}>${esc(c.label || `Column ${i + 1}`)}</th>`;
3969
+ }).join("")}</tr>`
3970
+ );
3971
+ fig.push("</thead>");
3972
+ }
3973
+ const rowHtml = (r) => {
3974
+ const cells = cols.map((c, i) => {
3975
+ const asTh = t.headerColumn && i === 0;
3976
+ const { inner, align } = cellHtml(t, c, r, isObj(r.cells) ? r.cells[c._key] : void 0, fn, asTh);
3977
+ const a = `${align !== "start" ? ` data-align="${align}"` : ""}${c.highlight ? ` data-highlight="true"` : ""}`;
3978
+ return asTh ? `<th scope="row"${a}>${inner}</th>` : `<td data-label="${escAttr(c.label || `Column ${i + 1}`)}"${a}>${inner}</td>`;
3979
+ });
3980
+ return `<tr${r.highlight ? ` data-highlight="true"` : ""}>${cells.join("")}</tr>`;
3981
+ };
3982
+ const { bodies, foot } = orderedRows(t);
3983
+ for (const b of bodies) {
3984
+ fig.push("<tbody>");
3985
+ if (b.group) fig.push(`<tr class="bt-group"><th scope="rowgroup" colspan="${n}">${esc(b.group.label)}</th></tr>`);
3986
+ fig.push(b.rows.map(rowHtml).join(""));
3987
+ fig.push("</tbody>");
3988
+ }
3989
+ if (foot.length) fig.push(`<tfoot>${foot.map(rowHtml).join("")}</tfoot>`);
3990
+ fig.push("</table></div>");
3991
+ if (hasNotes) {
3992
+ fig.push(`<div class="bt-notes" id="${id}-notes">`);
3993
+ if (t.notes) fig.push(`<p>${esc(t.notes)}</p>`);
3994
+ if (fn.size) fig.push(`<ol>${(t.footnotes ?? []).filter(isObj).map((f, i) => `<li id="${id}-fn-${i + 1}">${esc(f.text)}</li>`).join("")}</ol>`);
3995
+ fig.push("</div>");
3996
+ }
3997
+ fig.push("</figure>");
3998
+ const html = fig.join("");
3999
+ return responsive === "stack" || responsive === "cards" ? html.replace(/<table(?=[\s>])/, '<table role="table"').replace(/<(thead|tbody|tfoot)(?=[\s>])/g, '<$1 role="rowgroup"').replace(/<tr(?=[\s>])/g, '<tr role="row"').replace(/<th scope="(col|colgroup)"/g, '<th role="columnheader" scope="$1"').replace(/<th scope="(row|rowgroup)"/g, '<th role="rowheader" scope="$1"').replace(/<td(?=[\s>])/g, '<td role="cell"') : html;
4000
+ }
4001
+ var mdSafe = (s) => s.replace(/(\]\([^)]*\))|[<>]/g, (m, link) => link ?? (m === "<" ? "&lt;" : "&gt;"));
4002
+ var mdCell = (s) => mdSafe(s).replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\s*\n\s*/g, " ").trim();
4003
+ var inlineMd = (html) => strip(
4004
+ sanitizeInline(html).replace(/<(strong)>([\s\S]*?)<\/\1>/g, "**$2**").replace(/<(em)>([\s\S]*?)<\/\1>/g, "_$2_").replace(/<code>([\s\S]*?)<\/code>/g, "`$1`").replace(/<a href="([^"]*)">([\s\S]*?)<\/a>/g, (_m, h, l) => `[${l}](${h.replace(/&amp;/g, "&")})`).replace(/<br>/g, " ")
4005
+ );
4006
+ function tableToMarkdown(input) {
4007
+ const t = readTable(input);
4008
+ const cols = t.columns.filter(isObj);
4009
+ if (!cols.length) return "";
4010
+ const groups = new Map((t.columnGroups ?? []).filter(isObj).map((g) => [g._key, g.label]));
4011
+ const fn = new Map((t.footnotes ?? []).filter(isObj).map((f, i) => [f._key, i + 1]));
4012
+ const pfx = (t.id ?? "t").replace(/[^\w-]/g, "") || "t";
4013
+ const line = (cells) => `| ${cells.join(" | ")} |`;
4014
+ const head = cols.map((c, i) => mdCell([c.group && groups.get(c.group), c.label || (i === 0 && t.headerColumn ? "" : `Column ${i + 1}`)].filter(Boolean).join(" \xB7 ")) || " ");
4015
+ const sep = cols.map((c) => alignOf(c, c.type) === "end" ? "---:" : alignOf(c, c.type) === "center" ? ":---:" : "---");
4016
+ const row = (r) => line(
4017
+ cols.map((c, i) => {
4018
+ const cell = isObj(r.cells) ? r.cells[c._key] : void 0;
4019
+ const v = tableCellValue(cell);
4020
+ const ty = tableCellType(c, r, cell);
4021
+ let s = ty === "richtext-inline" && typeof v === "string" ? inlineMd(v) : tableCellText(c, r, cell);
4022
+ if (ty === "link" && isTableLink(v) && v.href && !isScriptHref(v.href)) s = `[${s}](${v.href})`;
4023
+ if (ty === "reference" && isTableRef(v)) {
4024
+ const href = refHref(v);
4025
+ if (href && !isScriptHref(href)) s = `[${s}](${href})`;
4026
+ }
4027
+ if (!s && !(t.headerColumn && i === 0)) s = "\u2014";
4028
+ if (t.headerColumn && i === 0 && s) s = `**${s}**`;
4029
+ const note = isTableCellObject(cell) ? cell.note : void 0;
4030
+ if (note && fn.get(note)) s += `[^${pfx}-${fn.get(note)}]`;
4031
+ return mdCell(s) || " ";
4032
+ })
4033
+ );
4034
+ const out = [`**${mdCell(t.caption || "Table")}**`, "", line(head), line(sep)];
4035
+ const { bodies, foot } = orderedRows(t);
4036
+ for (const b of bodies) {
4037
+ if (b.group) out.push(line([`**${mdCell(b.group.label)}**`, ...cols.slice(1).map(() => " ")]));
4038
+ b.rows.forEach((r) => out.push(row(r)));
4039
+ }
4040
+ foot.forEach((r) => out.push(row(r)));
4041
+ const after = [];
4042
+ if (t.notes) after.push(mdSafe(t.notes));
4043
+ const described = cols.filter((c) => c.description);
4044
+ if (described.length) after.push(mdSafe(described.map((c) => `${c.label}: ${c.description}`).join(". ") + "."));
4045
+ if (fn.size) after.push((t.footnotes ?? []).filter(isObj).map((f, i) => `[^${pfx}-${i + 1}]: ${mdSafe(f.text)}`).join("\n"));
4046
+ return [out.join("\n"), ...after].join("\n\n");
4047
+ }
4048
+ function tableSearchText(input) {
4049
+ if (!isTableValue(input)) return "";
4050
+ const t = input;
4051
+ const cols = t.columns.filter(isObj);
4052
+ const parts = [t.caption, t.notes ?? ""];
4053
+ for (const c of cols) parts.push(c.label, c.description ?? "");
4054
+ for (const g of [...t.columnGroups ?? [], ...t.rowGroups ?? []]) if (isObj(g)) parts.push(g.label);
4055
+ for (const r of t.rows) {
4056
+ if (!isObj(r) || !isObj(r.cells)) continue;
4057
+ for (const c of cols) {
4058
+ const cell = r.cells[c._key];
4059
+ parts.push(tableCellType(c, r, cell) === "richtext-inline" ? strip(String(tableCellValue(cell) ?? "")) : tableCellText(c, r, cell));
4060
+ }
4061
+ }
4062
+ for (const f of t.footnotes ?? []) if (isObj(f)) parts.push(f.text);
4063
+ return parts.filter((p) => typeof p === "string" && p.trim() !== "").map((p) => p.trim()).join(" ");
4064
+ }
4065
+
4066
+ // src/content-model.ts
4067
+ var OPTION_DESCRIPTION_MAX = 140;
4068
+
3035
4069
  // src/locator.ts
3036
4070
  var LANDMARK_SELECTOR = "header,nav,footer";
3037
4071
  var LANDMARK_TAGS = /* @__PURE__ */ new Set(["HEADER", "NAV", "FOOTER"]);
@@ -3092,6 +4126,96 @@ function regionOf(regions, el) {
3092
4126
  for (const r of regions) if (r.el.contains(el)) slot = r.slot;
3093
4127
  return slot;
3094
4128
  }
4129
+
4130
+ // src/sections.ts
4131
+ var TAG_HEADS = /* @__PURE__ */ new Set([
4132
+ "h1",
4133
+ "h2",
4134
+ "h3",
4135
+ "h4",
4136
+ "h5",
4137
+ "h6",
4138
+ "p",
4139
+ "a",
4140
+ "img",
4141
+ "li",
4142
+ "list",
4143
+ "blockquote",
4144
+ "figcaption",
4145
+ "button",
4146
+ "summary",
4147
+ "dt",
4148
+ "dd",
4149
+ "th",
4150
+ "td",
4151
+ "pre",
4152
+ "label",
4153
+ "span",
4154
+ "div",
4155
+ "time",
4156
+ "strong",
4157
+ "em",
4158
+ "small",
4159
+ "b",
4160
+ "i",
4161
+ "code",
4162
+ "icon",
4163
+ "bg"
4164
+ ]);
4165
+ var topFieldKeyOf = (path) => path.split(/[.[]/, 1)[0] ?? path;
4166
+ function fieldGroupOf(topKey, flat = false) {
4167
+ if (topKey.startsWith("group-")) return { groupKey: topKey, rooted: true };
4168
+ const camel = flat ? /^([a-z][a-z0-9]*)[A-Z]/.exec(topKey)?.[1] : void 0;
4169
+ if (camel) return TAG_HEADS.has(camel) ? { groupKey: topKey, rooted: false } : { groupKey: camel, rooted: true };
4170
+ const head = topKey.split(/[-_]/, 1)[0] || topKey;
4171
+ if (TAG_HEADS.has(head)) return { groupKey: topKey, rooted: false };
4172
+ return { groupKey: head, rooted: true };
4173
+ }
4174
+ function placementBindOf(block) {
4175
+ if (!block || typeof block !== "object" || block.type !== "component") return null;
4176
+ const props = block.props;
4177
+ const bind = props?.bind;
4178
+ if (typeof bind === "string" && bind) return bind;
4179
+ const source = props?.source;
4180
+ if (!source || typeof source !== "object") return null;
4181
+ const groups = Object.values(source).filter((path) => typeof path === "string" && path.length > 0).map((path) => ({ top: topFieldKeyOf(path), group: fieldGroupOf(topFieldKeyOf(path), path === topFieldKeyOf(path)).groupKey }));
4182
+ return (groups.find((g) => g.group !== g.top) ?? groups[0])?.group ?? null;
4183
+ }
4184
+ var CODE_SLOT_PREFIX = "code:";
4185
+ var slotBindOf = (slot) => slot.startsWith(CODE_SLOT_PREFIX) ? slot.slice(CODE_SLOT_PREFIX.length) : slot;
4186
+ function planSections(slots, blocks) {
4187
+ if (!Array.isArray(blocks)) return slots.map((slot) => ({ slot, block: null }));
4188
+ const byBind = /* @__PURE__ */ new Map();
4189
+ for (const slot of slots) if (!byBind.has(slotBindOf(slot))) byBind.set(slotBindOf(slot), slot);
4190
+ const out = [];
4191
+ const named = /* @__PURE__ */ new Set();
4192
+ for (const block of blocks) {
4193
+ const bind = placementBindOf(block);
4194
+ if (bind === null) {
4195
+ out.push({ slot: null, block });
4196
+ continue;
4197
+ }
4198
+ const slot = byBind.get(bind);
4199
+ if (slot === void 0 || named.has(slot)) continue;
4200
+ named.add(slot);
4201
+ out.push({ slot, block });
4202
+ }
4203
+ const held = named.size > 0;
4204
+ slots.forEach((slot, n) => {
4205
+ if (named.has(slot) || out.some((item) => item.slot === slot)) return;
4206
+ if (held && !slot.startsWith(CODE_SLOT_PREFIX)) return;
4207
+ let at = 0;
4208
+ for (let before = n - 1; before >= 0; before--) {
4209
+ const found = out.findIndex((item) => item.slot === slots[before]);
4210
+ if (found >= 0) {
4211
+ at = found + 1;
4212
+ break;
4213
+ }
4214
+ }
4215
+ out.splice(at, 0, { slot, block: null });
4216
+ });
4217
+ return out;
4218
+ }
3095
4219
  export {
3096
4220
  BLOCK_BINDINGS,
3097
4221
  BREAKPOINTS,
@@ -3101,6 +4225,7 @@ export {
3101
4225
  BUTTON_PLACEMENT_KEYS,
3102
4226
  BUTTON_SIZES,
3103
4227
  BUTTON_VARIANTS,
4228
+ CODE_SLOT_PREFIX,
3104
4229
  COMPONENT_PROP_TYPES,
3105
4230
  DIALOG_SELECTOR,
3106
4231
  DOCK_BUDGET,
@@ -3113,16 +4238,26 @@ export {
3113
4238
  LAYOUT_SECTION_ICONS,
3114
4239
  LAYOUT_SECTION_ICON_SET,
3115
4240
  LINK_RELS,
4241
+ LINK_VALUE_TYPES,
3116
4242
  MAIN_SELECTOR,
3117
4243
  NAVIGATION_SECTION_ID,
4244
+ OPTION_DESCRIPTION_MAX,
3118
4245
  PAGE_CONTENT_ANCHOR_ID,
3119
4246
  RESPONSIVE_CSS,
3120
4247
  SECTION_DOCTRINE,
3121
4248
  SECTION_SPACE_TOKENS,
4249
+ SLOT_CHILD_KEYS,
3122
4250
  SLOT_ITEMS_CAP,
3123
4251
  SLOT_MAX_ITEMS,
3124
4252
  SPACE_BOTTOM_TOKENS,
3125
4253
  SPACE_STEP_TOKENS,
4254
+ TABLE_COLUMN_TYPES,
4255
+ TABLE_JSONLD_TYPES,
4256
+ TABLE_LIMITS,
4257
+ TABLE_RESPONSIVE,
4258
+ TAG_HEADS,
4259
+ VIDEO_TRANSCRIPT_MAX,
4260
+ VIDEO_VALUE_KEYS,
3126
4261
  anchorTargetRel,
3127
4262
  blockStyleToColumnCss,
3128
4263
  blockStyleToCss,
@@ -3131,6 +4266,8 @@ export {
3131
4266
  buttonIconOf,
3132
4267
  buttonIconSvg,
3133
4268
  buttonLinkPairs,
4269
+ buttonLookValue,
4270
+ buttonSiteLookOf,
3134
4271
  buttonValueOfInstance,
3135
4272
  buttonValueProblem,
3136
4273
  buttonVariantOf,
@@ -3141,29 +4278,60 @@ export {
3141
4278
  dockBudget,
3142
4279
  dockScreens,
3143
4280
  expandBlockBindings,
4281
+ fieldGroupOf,
3144
4282
  fieldStyleCssText,
3145
4283
  fieldStyleToCss,
4284
+ formatNumber,
3146
4285
  formatPropsAttribute,
3147
4286
  getBlockType,
3148
4287
  headingPaintCss,
3149
4288
  hideOnAttr,
3150
4289
  inWindow,
3151
4290
  isBlock,
4291
+ isButtonLookValue,
3152
4292
  isChromeLandmark,
4293
+ isLinkObject,
4294
+ isScriptHref,
4295
+ isSlotChild,
4296
+ isTableValue,
3153
4297
  keyWords,
3154
4298
  landmarkRegions,
4299
+ linkAnchorOf,
4300
+ linkHref,
4301
+ linkRefOf,
4302
+ linkValueProblem,
3155
4303
  locatorFor,
4304
+ normalizeLinkValue,
4305
+ normalizeTableValue,
3156
4306
  normalizeWindow,
4307
+ parseNumberText,
3157
4308
  parsePropsAttribute,
3158
4309
  pickFieldStyle,
4310
+ placementBindOf,
4311
+ planSections,
3159
4312
  readButtonValue,
4313
+ readNumberFormat,
3160
4314
  readPath,
3161
4315
  regionOf,
4316
+ renderTableHtml,
3162
4317
  safeCssUrl,
4318
+ slotBindOf,
4319
+ slotChildValues,
3163
4320
  slotItems,
4321
+ slotPathString,
4322
+ slotShapeProblems,
3164
4323
  spaceTokenCss,
3165
4324
  splitButtonStyle,
4325
+ tableCellText,
4326
+ tablePathString,
4327
+ tableRefCells,
4328
+ tableSearchText,
4329
+ tableToMarkdown,
4330
+ tableValueProblems,
4331
+ topFieldKeyOf,
3166
4332
  usesHideOn,
4333
+ videoUrlOf,
4334
+ videoValueProblem,
3167
4335
  visibleBlocks,
3168
4336
  visibleDeliveredLayout,
3169
4337
  visibleLayoutStructure,