@bettercms-ai/types 1.41.0 → 1.43.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",
@@ -447,17 +532,76 @@ var COMPONENT_PROP_TYPES = [
447
532
  // CMS items, as the same-named content-model FIELDS store them: an entry id (or a polymorphic
448
533
  // `{ entryId, modelSlug }`), and a list of them. Delivery hydrates them like an entry's.
449
534
  "reference",
450
- "multi-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"
451
541
  ];
452
542
  var SLOT_MAX_ITEMS = 12;
453
543
  var SLOT_ITEMS_CAP = 50;
454
544
  function slotItems(value) {
455
545
  const list = Array.isArray(value) ? value : [value];
456
- return list.filter(
457
- (item) => !!item && typeof item === "object" && !Array.isArray(item) && typeof item.componentId === "string" && item.componentId !== ""
458
- );
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;
459
554
  }
460
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;
602
+ }
603
+ var slotPathString = (path) => path.map((s, i) => typeof s === "number" ? `[${s}]` : i === 0 ? s : `.${s}`).join("");
604
+
461
605
  // src/layout-lucide-icons.ts
462
606
  var LAYOUT_SECTION_ICONS = Object.freeze([
463
607
  "LayoutPanelTop",
@@ -2457,6 +2601,7 @@ function visibleDeliveredLayout(layout2, now) {
2457
2601
 
2458
2602
  // src/dock-icon-names.ts
2459
2603
  var DOCK_ICON_NAMES = [
2604
+ "abc",
2460
2605
  "access-denied",
2461
2606
  "activity",
2462
2607
  "add",
@@ -2464,14 +2609,40 @@ var DOCK_ICON_NAMES = [
2464
2609
  "add-comment",
2465
2610
  "add-document",
2466
2611
  "add-user",
2612
+ "address-book",
2613
+ "adjustments",
2614
+ "adjustments-horizontal",
2615
+ "alarm",
2616
+ "alert-triangle",
2617
+ "align-left",
2467
2618
  "api",
2619
+ "app-window",
2620
+ "apple",
2621
+ "apps",
2468
2622
  "archive",
2623
+ "arrow-back-up",
2469
2624
  "arrow-down",
2625
+ "arrow-guide",
2470
2626
  "arrow-left",
2471
2627
  "arrow-right",
2472
2628
  "arrow-top-right",
2473
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",
2474
2639
  "asterisk",
2640
+ "award",
2641
+ "axis-x",
2642
+ "backpack",
2643
+ "badge",
2644
+ "badge-cc",
2645
+ "badges",
2475
2646
  "bar-chart",
2476
2647
  "basket",
2477
2648
  "bell",
@@ -2483,25 +2654,76 @@ var DOCK_ICON_NAMES = [
2483
2654
  "bold",
2484
2655
  "bolt",
2485
2656
  "book",
2657
+ "book-2",
2486
2658
  "bookmark",
2487
2659
  "bookmark-filled",
2660
+ "books",
2488
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",
2489
2674
  "bug",
2675
+ "building",
2676
+ "building-skyscraper",
2677
+ "building-store",
2490
2678
  "bulb-filled",
2491
2679
  "bulb-outline",
2680
+ "calculator",
2492
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",
2493
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",
2494
2703
  "chart-upward",
2704
+ "check",
2705
+ "checkbox",
2706
+ "checklist",
2495
2707
  "checkmark",
2496
2708
  "checkmark-circle",
2709
+ "chef-hat",
2497
2710
  "chevron-down",
2498
2711
  "chevron-left",
2499
2712
  "chevron-right",
2500
2713
  "chevron-up",
2714
+ "chevrons-right",
2501
2715
  "circle",
2716
+ "circle-check",
2717
+ "circle-dot",
2718
+ "circle-number-1",
2719
+ "circle-plus",
2720
+ "click",
2502
2721
  "clipboard",
2503
2722
  "clipboard-image",
2723
+ "clipboard-text",
2504
2724
  "clock",
2725
+ "clock-check",
2726
+ "clock-hour-4",
2505
2727
  "close",
2506
2728
  "close-circle",
2507
2729
  "code",
@@ -2509,20 +2731,31 @@ var DOCK_ICON_NAMES = [
2509
2731
  "cog",
2510
2732
  "collapse",
2511
2733
  "color-wheel",
2734
+ "columns",
2735
+ "columns-2",
2736
+ "columns-3",
2512
2737
  "comment",
2513
2738
  "component",
2514
2739
  "compose",
2515
2740
  "compose-sparkles",
2516
2741
  "confetti",
2517
2742
  "controls",
2743
+ "cookie",
2518
2744
  "copy",
2519
2745
  "credit-card",
2520
2746
  "crop",
2521
2747
  "cube",
2748
+ "currency-dollar",
2522
2749
  "dashboard",
2523
2750
  "database",
2751
+ "database-search",
2524
2752
  "desktop",
2753
+ "device-desktop",
2754
+ "device-mobile",
2755
+ "device-tablet",
2756
+ "devices",
2525
2757
  "diamond",
2758
+ "discount",
2526
2759
  "document",
2527
2760
  "document-pdf",
2528
2761
  "document-remove",
@@ -2532,6 +2765,7 @@ var DOCK_ICON_NAMES = [
2532
2765
  "document-word",
2533
2766
  "document-zip",
2534
2767
  "documents",
2768
+ "door-exit",
2535
2769
  "dot",
2536
2770
  "double-chevron-down",
2537
2771
  "double-chevron-left",
@@ -2541,9 +2775,12 @@ var DOCK_ICON_NAMES = [
2541
2775
  "download",
2542
2776
  "drag-handle",
2543
2777
  "drop",
2778
+ "droplet",
2779
+ "droplet-half",
2544
2780
  "earth-americas",
2545
2781
  "earth-globe",
2546
2782
  "edit",
2783
+ "egg",
2547
2784
  "ellipsis-horizontal",
2548
2785
  "ellipsis-vertical",
2549
2786
  "empty",
@@ -2556,77 +2793,169 @@ var DOCK_ICON_NAMES = [
2556
2793
  "error-screen",
2557
2794
  "expand",
2558
2795
  "eye-closed",
2796
+ "eye-off",
2559
2797
  "eye-open",
2560
2798
  "face-happy",
2561
2799
  "face-indifferent",
2562
2800
  "face-sad",
2801
+ "feather",
2563
2802
  "feedback",
2803
+ "file",
2804
+ "file-certificate",
2805
+ "file-code",
2806
+ "file-download",
2807
+ "file-text",
2564
2808
  "filter",
2809
+ "flag",
2810
+ "flame",
2811
+ "flask",
2812
+ "focus-2",
2565
2813
  "folder",
2814
+ "folders",
2815
+ "forms",
2816
+ "frame",
2566
2817
  "generate",
2818
+ "gift",
2819
+ "git-branch",
2567
2820
  "github",
2821
+ "grid-dots",
2568
2822
  "groq",
2823
+ "hammer",
2824
+ "hand-finger",
2825
+ "hanger",
2569
2826
  "hash",
2827
+ "headphones",
2828
+ "headset",
2570
2829
  "heart",
2571
2830
  "heart-filled",
2831
+ "heart-handshake",
2572
2832
  "help-circle",
2573
2833
  "highlight",
2834
+ "history",
2574
2835
  "home",
2836
+ "hourglass",
2575
2837
  "ice-cream",
2838
+ "id-badge",
2839
+ "id-badge-2",
2576
2840
  "image",
2577
2841
  "image-remove",
2578
2842
  "images",
2579
2843
  "inbox",
2844
+ "infinity",
2845
+ "info-circle",
2580
2846
  "info-filled",
2581
2847
  "info-outline",
2848
+ "info-square-rounded",
2582
2849
  "inline",
2583
2850
  "inline-element",
2851
+ "input-search",
2584
2852
  "insert-above",
2585
2853
  "insert-below",
2586
2854
  "italic",
2587
2855
  "joystick",
2588
2856
  "json",
2857
+ "language",
2589
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",
2590
2875
  "leave",
2591
2876
  "lemon",
2877
+ "library-photo",
2878
+ "lifebuoy",
2592
2879
  "link",
2593
2880
  "link-removed",
2594
2881
  "linkedin",
2595
2882
  "list",
2883
+ "list-check",
2884
+ "list-details",
2885
+ "list-numbers",
2886
+ "list-tree",
2596
2887
  "lock",
2597
2888
  "logo-js",
2598
2889
  "logo-ts",
2890
+ "mail",
2891
+ "map-2",
2892
+ "map-pin",
2893
+ "map-pins",
2599
2894
  "marker",
2600
2895
  "marker-removed",
2601
2896
  "master-detail",
2897
+ "math-function",
2898
+ "meat",
2602
2899
  "menu",
2900
+ "message",
2901
+ "message-2",
2902
+ "message-check",
2903
+ "message-circle",
2904
+ "message-circle-2",
2905
+ "message-plus",
2906
+ "message-star",
2603
2907
  "microphone",
2604
2908
  "microphone-slash",
2605
2909
  "mobile-device",
2910
+ "mood-empty",
2606
2911
  "moon",
2912
+ "movie",
2913
+ "news",
2914
+ "notes",
2607
2915
  "number",
2608
2916
  "ok-hand",
2609
2917
  "olist",
2610
2918
  "overage",
2611
2919
  "package",
2920
+ "packages",
2921
+ "palette",
2612
2922
  "panel-left",
2613
2923
  "panel-right",
2924
+ "paperclip",
2614
2925
  "pause",
2926
+ "pencil",
2927
+ "phone",
2928
+ "photo",
2929
+ "photo-scan",
2930
+ "photo-search",
2931
+ "pig-money",
2615
2932
  "pin",
2616
2933
  "pin-filled",
2617
2934
  "pin-removed",
2618
2935
  "play",
2936
+ "player-play",
2937
+ "player-track-next",
2938
+ "playlist-add",
2619
2939
  "plug",
2940
+ "plug-connected",
2941
+ "plus-minus",
2942
+ "point",
2620
2943
  "presentation",
2944
+ "printer",
2621
2945
  "progress-50",
2622
2946
  "progress-75",
2623
2947
  "projects",
2624
2948
  "publish",
2949
+ "qrcode",
2950
+ "quote",
2625
2951
  "read-only",
2952
+ "receipt",
2953
+ "recycle",
2626
2954
  "redo",
2627
2955
  "refresh",
2628
2956
  "remove",
2629
2957
  "remove-circle",
2958
+ "repeat",
2630
2959
  "reset",
2631
2960
  "restore",
2632
2961
  "retrieve",
@@ -2634,49 +2963,90 @@ var DOCK_ICON_NAMES = [
2634
2963
  "revert",
2635
2964
  "robot",
2636
2965
  "rocket",
2966
+ "rosette",
2967
+ "route",
2968
+ "ruler",
2969
+ "ruler-measure",
2970
+ "salad",
2971
+ "scale",
2637
2972
  "schema",
2973
+ "school",
2638
2974
  "search",
2975
+ "section",
2639
2976
  "select",
2977
+ "send",
2978
+ "server",
2979
+ "settings",
2640
2980
  "share",
2981
+ "shield-check",
2982
+ "shield-lock",
2983
+ "shopping-bag",
2984
+ "shopping-cart",
2985
+ "shopping-cart-plus",
2986
+ "signature",
2987
+ "sitemap",
2988
+ "slideshow",
2641
2989
  "sort",
2990
+ "sort-descending",
2991
+ "spacing-vertical",
2642
2992
  "sparkle",
2643
2993
  "sparkles",
2994
+ "speakerphone",
2644
2995
  "spinner",
2645
2996
  "split-horizontal",
2646
2997
  "split-vertical",
2647
2998
  "square",
2648
2999
  "stack",
3000
+ "stack-2",
2649
3001
  "stack-compact",
3002
+ "stairs",
3003
+ "stairs-up",
2650
3004
  "star",
2651
3005
  "star-filled",
3006
+ "stars",
2652
3007
  "stop",
2653
3008
  "strikethrough",
2654
3009
  "string",
3010
+ "subtask",
2655
3011
  "sun",
3012
+ "superscript",
2656
3013
  "sync",
3014
+ "table",
3015
+ "table-options",
2657
3016
  "tablet-device",
2658
3017
  "tag",
2659
3018
  "tags",
2660
3019
  "target",
2661
3020
  "task",
3021
+ "template",
3022
+ "tent",
2662
3023
  "terminal",
3024
+ "terminal-2",
2663
3025
  "text",
2664
3026
  "th-large",
2665
3027
  "th-list",
3028
+ "thumb-up",
2666
3029
  "thumbs-down",
2667
3030
  "thumbs-up",
3031
+ "ticket",
2668
3032
  "tiers",
2669
3033
  "timeline",
2670
3034
  "toggle-arrow-right",
3035
+ "toggle-right",
2671
3036
  "token",
3037
+ "tool",
3038
+ "tools-kitchen-2",
2672
3039
  "transfer",
2673
3040
  "translate",
2674
3041
  "trash",
2675
3042
  "trend-upward",
2676
3043
  "triangle-outline",
2677
3044
  "trolley",
3045
+ "truck",
3046
+ "truck-delivery",
2678
3047
  "truncate",
2679
3048
  "twitter",
3049
+ "typography",
2680
3050
  "ulist",
2681
3051
  "unarchive",
2682
3052
  "underline",
@@ -2687,12 +3057,22 @@ var DOCK_ICON_NAMES = [
2687
3057
  "unpublish",
2688
3058
  "upload",
2689
3059
  "user",
3060
+ "user-circle",
3061
+ "user-plus",
3062
+ "user-search",
3063
+ "user-star",
2690
3064
  "users",
3065
+ "users-group",
2691
3066
  "versions",
2692
3067
  "video",
3068
+ "vocabulary",
3069
+ "vs",
2693
3070
  "warning-filled",
2694
3071
  "warning-outline",
2695
- "wrench"
3072
+ "weight",
3073
+ "world",
3074
+ "wrench",
3075
+ "zoom-question"
2696
3076
  ];
2697
3077
  var DOCK_ICON_NAME_SET = new Set(DOCK_ICON_NAMES);
2698
3078
 
@@ -2703,7 +3083,7 @@ function listItems(f) {
2703
3083
  if (f.type === "repeater") return f.fields ?? [];
2704
3084
  if (f.type === "table") return c.fields ?? f.fields ?? [];
2705
3085
  if (f.type === "array" && c.zones?.repeatable) return c.zones.repeatable.fields ?? [];
2706
- if (f.type === "modular") return null;
3086
+ if (f.type === "modular" || f.type === "slot") return null;
2707
3087
  return void 0;
2708
3088
  }
2709
3089
  function zoneFields(f) {
@@ -2719,7 +3099,7 @@ function layout(fields, fieldsets, root) {
2719
3099
  const nonEmpty = new Set(
2720
3100
  fields.map((f) => tabOf(byId.get(f.fieldsetId ?? ""))).filter((t) => t && tabIds.includes(t))
2721
3101
  );
2722
- const strip = nonEmpty.size >= 2;
3102
+ const strip2 = nonEmpty.size >= 2;
2723
3103
  const branches = /* @__PURE__ */ new Map();
2724
3104
  const nodeFor = (f, path) => {
2725
3105
  const key = `${path}${f.key}`;
@@ -2740,12 +3120,12 @@ function layout(fields, fieldsets, root) {
2740
3120
  return { kind: "leaf", key };
2741
3121
  };
2742
3122
  const pinned = [];
2743
- 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, []]) : []);
2744
3124
  const containers = /* @__PURE__ */ new Map();
2745
3125
  for (const f of fields) {
2746
3126
  const node = nodeFor(f, "");
2747
3127
  const set = byId.get(f.fieldsetId ?? "");
2748
- const target = strip && panels.get(tabOf(set) ?? "") || pinned;
3128
+ const target = strip2 && panels.get(tabOf(set) ?? "") || pinned;
2749
3129
  if (set?.kind !== "group" && set?.kind !== "row") {
2750
3130
  target.push(node);
2751
3131
  continue;
@@ -3053,27 +3433,47 @@ function anchorTargetRel(newTab, rel) {
3053
3433
  const tokens = [...newTab ? ["noopener", "noreferrer"] : [], ...LINK_RELS.includes(rel) ? [rel] : []];
3054
3434
  return `${newTab ? ' target="_blank"' : ""}${tokens.length ? ` rel="${tokens.join(" ")}"` : ""}`;
3055
3435
  }
3436
+ var BUTTON_ACTIONS = ["code"];
3437
+ function isCodeButton(value) {
3438
+ return !!value && typeof value === "object" && value.action === "code";
3439
+ }
3056
3440
  var hrefOf = linkHref;
3057
3441
  function readButtonValue(value) {
3058
3442
  const v = value && typeof value === "object" ? value : {};
3059
3443
  const link = isLinkObject(v.href) ? linkAnchorOf(v.href) : null;
3060
3444
  const rel = v.rel ?? link?.rel;
3445
+ const code = v.action === "code";
3061
3446
  return {
3062
3447
  label: typeof v.label === "string" ? v.label : "",
3063
- href: hrefOf(v.href) ?? "",
3448
+ href: code ? "" : hrefOf(v.href) ?? "",
3064
3449
  variant: buttonVariantOf(v.variant),
3065
3450
  size: BUTTON_SIZES.includes(v.size) ? v.size : "md",
3066
3451
  arrow: v.arrow === true,
3067
3452
  icon: buttonIconOf(v.icon),
3068
- newTab: v.newTab === void 0 ? link?.newTab === true : v.newTab === true,
3069
- ...LINK_RELS.includes(rel) ? { rel } : {}
3453
+ newTab: code ? false : v.newTab === void 0 ? link?.newTab === true : v.newTab === true,
3454
+ ...!code && LINK_RELS.includes(rel) ? { rel } : {},
3455
+ ...code ? { action: "code" } : {},
3456
+ ...v.fullWidth === true ? { fullWidth: true } : {}
3070
3457
  };
3071
3458
  }
3459
+ var BUTTON_FULL_WIDTH_INVALID = "button_full_width_invalid";
3460
+ function buttonFullWidthProblem(value, previous) {
3461
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3462
+ const fullWidth = value.fullWidth;
3463
+ if (fullWidth === void 0 || typeof fullWidth === "boolean") return null;
3464
+ const before = previous && typeof previous === "object" ? previous.fullWidth : void 0;
3465
+ if (before !== void 0 && JSON.stringify(before) === JSON.stringify(fullWidth)) return null;
3466
+ return `${BUTTON_FULL_WIDTH_INVALID}: fullWidth must be true or false`;
3467
+ }
3072
3468
  function buttonValueProblem(value, looks) {
3073
3469
  if (!value || typeof value !== "object" || Array.isArray(value)) return "must be a { label, href } button";
3074
3470
  const v = value;
3075
3471
  if (typeof v.label !== "string") return "needs a label";
3076
- if (hrefOf(v.href) === null) return "needs an href";
3472
+ if (v.action !== void 0 && !BUTTON_ACTIONS.includes(v.action)) return `has an unknown action "${String(v.action)}" (code, or none for a link)`;
3473
+ if (v.action === "code") {
3474
+ if (v.href !== void 0 && v.href !== null && v.href !== "") return 'takes no href with action "code"';
3475
+ if (v.newTab === true || v.rel !== void 0) return 'takes no newTab or rel with action "code"';
3476
+ } else if (hrefOf(v.href) === null) return `needs an href (or action "code" for a button the site's script handles)`;
3077
3477
  if (isLinkObject(v.href)) {
3078
3478
  const problem = linkValueProblem(v.href);
3079
3479
  if (problem) return `href ${problem}`;
@@ -3088,12 +3488,18 @@ function buttonValueProblem(value, looks) {
3088
3488
  if (v.icon !== void 0 && v.icon !== null && buttonIconOf(v.icon) === null) return `has an unknown icon "${String(v.icon)}"`;
3089
3489
  return null;
3090
3490
  }
3091
- var BUTTON_LOOK_KEYS = ["variant", "size", "arrow", "icon", "newTab"];
3491
+ var BUTTON_LOOK_KEYS = ["variant", "size", "arrow", "icon", "newTab", "fullWidth"];
3092
3492
  function buttonBlockProps(value) {
3093
3493
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
3094
3494
  const v = value;
3095
3495
  const out = {};
3096
3496
  if (typeof v.label === "string") out.text = v.label;
3497
+ if (v.action === "code") {
3498
+ out.action = "code";
3499
+ out.href = "";
3500
+ for (const key of BUTTON_LOOK_KEYS) if (key in v && key !== "newTab") out[key] = v[key];
3501
+ return out;
3502
+ }
3097
3503
  const href = hrefOf(v.href);
3098
3504
  const script = href !== null && isScriptHref(href);
3099
3505
  if (href !== null) out.href = script ? "#" : href;
@@ -3119,8 +3525,8 @@ function soleButtonId(blockJson) {
3119
3525
  walk(blockJson);
3120
3526
  return ids.length === 1 ? ids[0] : null;
3121
3527
  }
3122
- var LOOK_PROP_TYPE = { variant: "text", size: "text", arrow: "boolean", icon: "text", newTab: "boolean" };
3123
- var LOOK_PROP_LABEL = { variant: "Style", size: "Size", arrow: "Show arrow", icon: "Leading icon", newTab: "Open in new tab" };
3528
+ var LOOK_PROP_TYPE = { variant: "text", size: "text", arrow: "boolean", icon: "text", newTab: "boolean", fullWidth: "boolean" };
3529
+ var LOOK_PROP_LABEL = { variant: "Style", size: "Size", arrow: "Show arrow", icon: "Leading icon", newTab: "Open in new tab", fullWidth: "Full width" };
3124
3530
  function withButtonLookProps(def) {
3125
3531
  const props = [...def.props ?? []];
3126
3532
  if (def.category !== "button") return props;
@@ -3155,6 +3561,534 @@ function buttonValueOfInstance(def, overrides) {
3155
3561
  return readButtonValue({ ...props, label: props.text });
3156
3562
  }
3157
3563
 
3564
+ // src/video-value.ts
3565
+ var VIDEO_VALUE_KEYS = ["url", "poster", "autoplay", "loop", "transcript"];
3566
+ var VIDEO_TRANSCRIPT_MAX = 1e5;
3567
+ var isPlainObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
3568
+ var isVideoUrl = (s) => s === "" || /^https?:\/\/[^\s]+$/i.test(s.trim()) || /^\/(?!\/)\S*$/.test(s.trim());
3569
+ function videoUrlOf(value) {
3570
+ const url = typeof value === "string" ? value : isPlainObject2(value) ? value.url : void 0;
3571
+ return typeof url === "string" && url.trim() !== "" ? url.trim() : null;
3572
+ }
3573
+ function videoValueProblem(value) {
3574
+ if (typeof value === "string") return isVideoUrl(value) ? null : "video_value_invalid: url must be an http(s) URL or a site path";
3575
+ if (!isPlainObject2(value)) return "video_value_invalid: must be a URL string or { url, poster?, autoplay?, loop?, transcript? }";
3576
+ const unknown = Object.keys(value).find((k) => !VIDEO_VALUE_KEYS.includes(k));
3577
+ if (unknown !== void 0) return `video_value_invalid: unknown key "${unknown}" (${VIDEO_VALUE_KEYS.join(", ")})`;
3578
+ if (typeof value.url !== "string") return "video_value_invalid: needs a url string";
3579
+ if (!isVideoUrl(value.url)) return "video_value_invalid: url must be an http(s) URL or a site path";
3580
+ if (value.poster !== void 0 && (typeof value.poster !== "string" || !isVideoUrl(value.poster))) {
3581
+ return "video_value_invalid: poster must be an http(s) URL or a site path";
3582
+ }
3583
+ for (const key of ["autoplay", "loop"]) {
3584
+ if (value[key] !== void 0 && typeof value[key] !== "boolean") return `video_value_invalid: ${key} must be true or false`;
3585
+ }
3586
+ if (value.transcript !== void 0 && (typeof value.transcript !== "string" || value.transcript.length > VIDEO_TRANSCRIPT_MAX)) {
3587
+ return `video_value_invalid: transcript must be a string of ${VIDEO_TRANSCRIPT_MAX} characters or fewer`;
3588
+ }
3589
+ return null;
3590
+ }
3591
+
3592
+ // src/table-value.ts
3593
+ var TABLE_COLUMN_TYPES = ["text", "number", "money", "boolean", "rating", "link", "richtext-inline", "reference"];
3594
+ var TABLE_RESPONSIVE = ["scroll", "stack", "cards"];
3595
+ var TABLE_JSONLD_TYPES = ["none", "ItemList", "Dataset", "Offers"];
3596
+ var TABLE_LIMITS = { columns: 50, rows: 1e3, cellChars: 5e3, captionChars: 300, notesChars: 2e3, keyChars: 64, labelChars: 300 };
3597
+ var isObj = (v) => !!v && typeof v === "object" && !Array.isArray(v);
3598
+ var isTableValue = (v) => isObj(v) && Array.isArray(v.columns) && Array.isArray(v.rows);
3599
+ var isTableCellObject = (c) => isObj(c) && "value" in c;
3600
+ var isTableRef = (v) => isObj(v) && "ref" in v;
3601
+ var isTableLink = (v) => isObj(v) && !("ref" in v) && "href" in v;
3602
+ var tableCellValue = (c) => c === void 0 ? null : isTableCellObject(c) ? c.value : c;
3603
+ var tableCellType = (col, row, c) => isTableCellObject(c) && c.type || row?.type || col.type;
3604
+ var TOP_KEYS = /* @__PURE__ */ new Set(["id", "caption", "notes", "headerRow", "headerColumn", "columns", "columnGroups", "rowGroups", "rows", "footnotes", "filterable", "design", "jsonLd"]);
3605
+ var COLUMN_KEYS = /* @__PURE__ */ new Set(["_key", "label", "type", "description", "unit", "currency", "decimals", "align", "sortable", "group", "trueLabel", "falseLabel", "max", "refTo", "highlight"]);
3606
+ var ROW_KEYS = /* @__PURE__ */ new Set(["_key", "cells", "group", "footer", "type", "highlight"]);
3607
+ var GROUP_KEYS = /* @__PURE__ */ new Set(["_key", "label"]);
3608
+ var FOOTNOTE_KEYS = /* @__PURE__ */ new Set(["_key", "text"]);
3609
+ var DESIGN_KEYS = /* @__PURE__ */ new Set(["responsive", "sticky", "striped", "density", "hideCaption"]);
3610
+ var CELL_KEYS = /* @__PURE__ */ new Set(["value", "type", "note"]);
3611
+ var LINK_KEYS = /* @__PURE__ */ new Set(["label", "href", "newTab", "rel"]);
3612
+ var REF_KEYS = /* @__PURE__ */ new Set(["ref", "label", "href", "resolved"]);
3613
+ var TABLE_LINK_RELS = ["follow", ...LINK_RELS];
3614
+ function tableValueProblems(value, opts = {}) {
3615
+ const out = [];
3616
+ const bad = (path, message, code = "table_value_invalid") => out.push({ path, code, message: `${code}: ${message}` });
3617
+ if (!isTableValue(value)) {
3618
+ bad([], "a table is { caption, headerRow, headerColumn, columns: [{ _key, label, type }], rows: [{ _key, cells }] }");
3619
+ return out;
3620
+ }
3621
+ const t = value;
3622
+ const unknownKeys = (o, allowed, at) => {
3623
+ for (const k of Object.keys(o)) if (!allowed.has(k)) bad([...at, k], `unknown key "${k}" (${[...allowed].join(", ")})`);
3624
+ };
3625
+ const str = (v, at, what, max, required = false) => {
3626
+ if (v === void 0 && !required) return;
3627
+ if (typeof v !== "string") bad(at, `${what} must be a string`);
3628
+ else if (v.length > max) bad(at, `${what} is ${v.length} characters; at most ${max}`, "table_limit_exceeded");
3629
+ };
3630
+ const bool = (v, at, what) => {
3631
+ if (v !== void 0 && typeof v !== "boolean") bad(at, `${what} must be true or false`);
3632
+ };
3633
+ const oneOf = (v, at, what, allowed) => {
3634
+ if (v !== void 0 && !allowed.includes(v)) bad(at, `${what} must be one of ${allowed.join(", ")}`);
3635
+ };
3636
+ unknownKeys(t, TOP_KEYS, []);
3637
+ str(t.id, ["id"], "id", TABLE_LIMITS.keyChars);
3638
+ str(t.caption, ["caption"], "caption", TABLE_LIMITS.captionChars, true);
3639
+ if (typeof t.caption === "string" && t.caption.trim() === "" && opts.requireCaption !== false) {
3640
+ bad(["caption"], "give the table a caption that says what it compares (it names the table for screen readers and search)", "table_caption_required");
3641
+ }
3642
+ str(t.notes, ["notes"], "notes", TABLE_LIMITS.notesChars);
3643
+ 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)");
3644
+ bool(t.headerRow, ["headerRow"], "headerRow");
3645
+ bool(t.headerColumn, ["headerColumn"], "headerColumn");
3646
+ bool(t.filterable, ["filterable"], "filterable");
3647
+ if (t.design !== void 0) {
3648
+ if (!isObj(t.design)) bad(["design"], "design must be an object");
3649
+ else {
3650
+ const d = t.design;
3651
+ unknownKeys(d, DESIGN_KEYS, ["design"]);
3652
+ oneOf(d.responsive, ["design", "responsive"], "design.responsive", TABLE_RESPONSIVE);
3653
+ oneOf(d.density, ["design", "density"], "design.density", ["compact", "comfortable"]);
3654
+ for (const k of ["sticky", "striped", "hideCaption"]) bool(d[k], ["design", k], `design.${k}`);
3655
+ }
3656
+ }
3657
+ const keyed = (list, at, keys, textKey, allowed) => {
3658
+ const seen = /* @__PURE__ */ new Set();
3659
+ if (list === void 0) return seen;
3660
+ if (!Array.isArray(list)) {
3661
+ bad([at], `${at} must be an array`);
3662
+ return seen;
3663
+ }
3664
+ if (!allowed && list.length > 0) bad([at], `this table takes no ${at === "footnotes" ? "footnotes (config.allowFootnotes is false)" : "row groups (config.allowGroups is false)"}`);
3665
+ list.forEach((g, i) => {
3666
+ if (!isObj(g)) return bad([at, i], `${at}[${i}] must be an object`);
3667
+ unknownKeys(g, keys, [at, i]);
3668
+ 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`);
3669
+ else if (seen.has(g._key)) bad([at, i, "_key"], `_key "${g._key}" is used twice`);
3670
+ else seen.add(g._key);
3671
+ str(g[textKey], [at, i, textKey], textKey, textKey === "text" ? TABLE_LIMITS.notesChars : TABLE_LIMITS.labelChars, true);
3672
+ });
3673
+ return seen;
3674
+ };
3675
+ const colGroups = keyed(t.columnGroups, "columnGroups", GROUP_KEYS, "label", true);
3676
+ const rowGroups = keyed(t.rowGroups, "rowGroups", GROUP_KEYS, "label", opts.allowGroups !== false);
3677
+ const notes = keyed(t.footnotes, "footnotes", FOOTNOTE_KEYS, "text", opts.allowFootnotes !== false);
3678
+ const columns = t.columns;
3679
+ const maxColumns = Math.min(opts.maxColumns ?? TABLE_LIMITS.columns, TABLE_LIMITS.columns);
3680
+ if (columns.length > maxColumns) bad(["columns"], `${columns.length} columns; at most ${maxColumns}`, "table_limit_exceeded");
3681
+ const cols = /* @__PURE__ */ new Map();
3682
+ columns.forEach((c, i) => {
3683
+ const at = ["columns", i];
3684
+ if (!isObj(c)) return bad(at, `columns[${i}] must be an object`);
3685
+ unknownKeys(c, COLUMN_KEYS, at);
3686
+ 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`);
3687
+ else if (cols.has(c._key)) bad([...at, "_key"], `_key "${c._key}" is used twice`);
3688
+ str(c.label, [...at, "label"], "label", TABLE_LIMITS.labelChars, true);
3689
+ if (!TABLE_COLUMN_TYPES.includes(c.type)) bad([...at, "type"], `type must be one of ${TABLE_COLUMN_TYPES.join(", ")}`);
3690
+ else if (opts.columnTypes && !opts.columnTypes.includes(c.type)) {
3691
+ bad([...at, "type"], `a ${String(c.type)} column is not offered here (config.columnTypes: ${opts.columnTypes.join(", ")})`, "table_column_type_not_allowed");
3692
+ }
3693
+ str(c.description, [...at, "description"], "description", TABLE_LIMITS.notesChars);
3694
+ str(c.unit, [...at, "unit"], "unit", 16);
3695
+ 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");
3696
+ 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");
3697
+ 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");
3698
+ oneOf(c.align, [...at, "align"], "align", ["start", "center", "end"]);
3699
+ for (const k of ["sortable", "highlight"]) bool(c[k], [...at, k], k);
3700
+ str(c.trueLabel, [...at, "trueLabel"], "trueLabel", TABLE_LIMITS.labelChars);
3701
+ str(c.falseLabel, [...at, "falseLabel"], "falseLabel", TABLE_LIMITS.labelChars);
3702
+ 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`);
3703
+ 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"');
3704
+ if (typeof c._key === "string" && c._key !== "" && !cols.has(c._key)) cols.set(c._key, c);
3705
+ });
3706
+ if (opts.lockColumns && opts.lockedColumns) {
3707
+ const want = opts.lockedColumns.map((c) => `${c._key}:${c.type}`).join(",");
3708
+ const got = columns.map((c) => isObj(c) ? `${String(c._key)}:${String(c.type)}` : "?").join(",");
3709
+ 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");
3710
+ }
3711
+ const rows = t.rows;
3712
+ const maxRows = Math.min(opts.maxRows ?? TABLE_LIMITS.rows, TABLE_LIMITS.rows);
3713
+ const body = rows.filter((r) => !(isObj(r) && r.footer === true)).length;
3714
+ if (rows.length > TABLE_LIMITS.rows || body > maxRows) bad(["rows"], `${body} rows; at most ${maxRows}`, "table_limit_exceeded");
3715
+ if (opts.minRows !== void 0 && body < opts.minRows) bad(["rows"], `${body} rows; at least ${opts.minRows}`, "table_rows_min");
3716
+ const rowKeys = /* @__PURE__ */ new Set();
3717
+ rows.forEach((r, i) => {
3718
+ const at = ["rows", i];
3719
+ if (!isObj(r)) return bad(at, `rows[${i}] must be an object`);
3720
+ unknownKeys(r, ROW_KEYS, at);
3721
+ 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`);
3722
+ else if (rowKeys.has(r._key)) bad([...at, "_key"], `_key "${r._key}" is used twice`);
3723
+ else rowKeys.add(r._key);
3724
+ 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`);
3725
+ bool(r.footer, [...at, "footer"], "footer");
3726
+ bool(r.highlight, [...at, "highlight"], "highlight");
3727
+ oneOf(r.type, [...at, "type"], "type", TABLE_COLUMN_TYPES);
3728
+ if (!isObj(r.cells)) return bad([...at, "cells"], "cells must be an object keyed by column _key");
3729
+ for (const [k, cell] of Object.entries(r.cells)) {
3730
+ const cAt = [...at, "cells", k];
3731
+ const col = cols.get(k);
3732
+ if (!col) {
3733
+ bad(cAt, `no column has _key "${k}"`);
3734
+ continue;
3735
+ }
3736
+ cellProblems(cell, col, r, cAt, notes, opts, bad);
3737
+ }
3738
+ });
3739
+ return out;
3740
+ }
3741
+ function cellProblems(cell, col, row, at, notes, opts, bad) {
3742
+ if (cell === void 0 || cell === null) return;
3743
+ let v = cell;
3744
+ let vAt = at;
3745
+ if (isTableCellObject(cell)) {
3746
+ for (const k of Object.keys(cell)) if (!CELL_KEYS.has(k)) bad([...at, k], `unknown key "${k}" (value, type, note)`);
3747
+ if (cell.type !== void 0 && !TABLE_COLUMN_TYPES.includes(cell.type)) bad([...at, "type"], `type must be one of ${TABLE_COLUMN_TYPES.join(", ")}`);
3748
+ 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`);
3749
+ v = cell.value;
3750
+ vAt = [...at, "value"];
3751
+ if (v === null) return;
3752
+ }
3753
+ const type = tableCellType(col, row, cell);
3754
+ if (typeof v === "string") {
3755
+ if (v.length > TABLE_LIMITS.cellChars) bad(vAt, `a cell holds at most ${TABLE_LIMITS.cellChars} characters`, "table_limit_exceeded");
3756
+ return;
3757
+ }
3758
+ if (typeof v === "number") {
3759
+ if (!Number.isFinite(v)) bad(vAt, "a number cell must be finite");
3760
+ return;
3761
+ }
3762
+ if (typeof v === "boolean") return;
3763
+ if (!isObj(v)) return bad(vAt, "a cell holds text, a number, true / false, a link { label, href } or a reference { ref }");
3764
+ if ("ref" in v) {
3765
+ if (type !== "reference") return bad(vAt, `a reference { ref } belongs in a reference cell; this cell is ${type}`);
3766
+ for (const k of Object.keys(v)) if (!REF_KEYS.has(k)) bad([...vAt, k], `unknown key "${k}" (ref, label, href)`);
3767
+ if (typeof v.ref !== "string" || v.ref === "") bad([...vAt, "ref"], "ref must be the id of a page or an entry");
3768
+ if (v.label !== void 0 && typeof v.label !== "string") bad([...vAt, "label"], "label must be a string");
3769
+ 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");
3770
+ return;
3771
+ }
3772
+ if (type !== "link") return bad(vAt, `a link { label, href } belongs in a link cell; this cell is ${type}`);
3773
+ for (const k of Object.keys(v)) if (!LINK_KEYS.has(k)) bad([...vAt, k], `unknown key "${k}" (label, href, newTab, rel)`);
3774
+ if (typeof v.label !== "string" && v.label !== void 0) bad([...vAt, "label"], "label must be a string");
3775
+ if (typeof v.href !== "string") return bad([...vAt, "href"], "needs an href string", "link_value_invalid");
3776
+ if (isScriptHref(v.href)) return bad([...vAt, "href"], "script links (javascript:, data:, vbscript:) aren't allowed", "link_value_invalid");
3777
+ const grammar = v.href !== "" ? opts.hrefProblem?.(v.href) : null;
3778
+ if (grammar) bad([...vAt, "href"], grammar, "link_value_invalid");
3779
+ if (v.newTab !== void 0 && typeof v.newTab !== "boolean") bad([...vAt, "newTab"], "newTab must be true or false", "link_value_invalid");
3780
+ 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");
3781
+ }
3782
+ function tablePathString(path) {
3783
+ return path.map((s, i) => typeof s === "number" ? `[${s}]` : i === 0 ? s : `.${s}`).join("");
3784
+ }
3785
+ function normalizeTableValue(value) {
3786
+ if (!isTableValue(value)) return value;
3787
+ const t = structuredClone(value);
3788
+ t.headerRow = t.headerRow !== false;
3789
+ t.headerColumn = t.headerColumn === true;
3790
+ for (const r of t.rows) {
3791
+ if (!isObj(r) || !isObj(r.cells)) continue;
3792
+ for (const c of Object.values(r.cells)) {
3793
+ const v = isTableCellObject(c) ? c.value : c;
3794
+ if (isTableRef(v)) delete v.resolved;
3795
+ }
3796
+ }
3797
+ return t;
3798
+ }
3799
+ var readTable = (v) => ({ ...v, headerRow: v.headerRow !== false, headerColumn: v.headerColumn === true });
3800
+ function tableRefCells(value) {
3801
+ if (!isTableValue(value)) return [];
3802
+ const cols = new Map(value.columns.filter(isObj).map((c) => [c._key, c]));
3803
+ const out = [];
3804
+ value.rows.forEach((r, i) => {
3805
+ if (!isObj(r) || !isObj(r.cells)) return;
3806
+ for (const [k, c] of Object.entries(r.cells)) {
3807
+ const col = cols.get(k);
3808
+ if (!col) continue;
3809
+ const v = isTableCellObject(c) ? c.value : c;
3810
+ if (!isTableRef(v) || typeof v.ref !== "string" || v.ref === "" || tableCellType(col, r, c) !== "reference") continue;
3811
+ 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 });
3812
+ }
3813
+ });
3814
+ return out;
3815
+ }
3816
+ var strip = (s) => s.replace(/<[^>]*>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
3817
+ var TRUE_WORDS = /* @__PURE__ */ new Set(["\u2713", "\u2714", "\u2714\uFE0F", "\u2611", "\u2705", "yes", "y", "true", "included", "available", "supported", "check", "\u25CF"]);
3818
+ var FALSE_WORDS = /* @__PURE__ */ new Set(["\u2717", "\u2715", "\u2716", "\u274C", "\xD7", "x", "no", "n", "false", "not included", "unavailable", "not available", "unsupported", "\u2014", "\u2013", "-", "\u25CB"]);
3819
+ function toBool(v) {
3820
+ if (typeof v === "boolean") return v;
3821
+ if (typeof v !== "string") return null;
3822
+ const s = strip(v).trim().toLowerCase();
3823
+ return TRUE_WORDS.has(s) ? true : FALSE_WORDS.has(s) ? false : null;
3824
+ }
3825
+ function toNumber(v) {
3826
+ if (typeof v === "number") return Number.isFinite(v) ? v : null;
3827
+ if (typeof v !== "string") return null;
3828
+ const s = v.replace(/[,\s]/g, "").replace(/[^\d.+-]/g, "");
3829
+ return s && /^[+-]?\d*\.?\d+$/.test(s) ? Number(s) : null;
3830
+ }
3831
+ var formatNumber2 = (n, decimals) => new Intl.NumberFormat("en-US", decimals != null ? { minimumFractionDigits: decimals, maximumFractionDigits: decimals } : { maximumFractionDigits: 6 }).format(n);
3832
+ function formatMoney(n, currency = "USD", decimals) {
3833
+ try {
3834
+ const whole = Number.isInteger(n) && decimals == null;
3835
+ return new Intl.NumberFormat("en-US", { style: "currency", currency, minimumFractionDigits: whole ? 0 : decimals ?? 2, maximumFractionDigits: whole ? 0 : decimals ?? 2 }).format(n);
3836
+ } catch {
3837
+ return `${formatNumber2(n, decimals)} ${currency}`;
3838
+ }
3839
+ }
3840
+ var isEmptyCell = (v) => v == null || v === "" || isObj(v) && !(isTableRef(v) ? v.ref : v.label || v.href);
3841
+ var refLabel = (r) => r.resolved?.title || r.label || r.ref;
3842
+ var refHref = (r) => r.resolved?.url ?? r.href;
3843
+ function tableCellText(col, row, c) {
3844
+ const v = tableCellValue(c);
3845
+ if (isEmptyCell(v)) return "";
3846
+ switch (tableCellType(col, row, c)) {
3847
+ case "boolean": {
3848
+ const b = toBool(v);
3849
+ return b == null ? strip(String(v)) : b ? col.trueLabel || "Included" : col.falseLabel || "Not included";
3850
+ }
3851
+ case "number": {
3852
+ const n = toNumber(v);
3853
+ return n == null ? strip(String(v)) : `${formatNumber2(n, col.decimals)}${col.unit ? /^[%°]/.test(col.unit) ? col.unit : ` ${col.unit}` : ""}`;
3854
+ }
3855
+ case "money": {
3856
+ const n = toNumber(v);
3857
+ return n == null ? strip(String(v)) : formatMoney(n, col.currency || "USD", col.decimals);
3858
+ }
3859
+ case "rating": {
3860
+ const n = toNumber(v);
3861
+ return n == null ? strip(String(v)) : `${formatNumber2(n, 1).replace(/\.0$/, "")} out of ${col.max ?? 5}`;
3862
+ }
3863
+ case "link":
3864
+ return isObj(v) ? String(v.label || v.href || "") : strip(String(v));
3865
+ case "reference":
3866
+ return isTableRef(v) ? refLabel(v) : strip(String(v));
3867
+ default:
3868
+ return isObj(v) ? String(v.label ?? "") : strip(String(v)).replace(/\s+/g, " ").trim();
3869
+ }
3870
+ }
3871
+ var esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3872
+ var escAttr = (s) => esc(s).replace(/"/g, "&quot;");
3873
+ var safeHref = (h) => isScriptHref(h) ? "#" : h;
3874
+ var INLINE_TAGS = /* @__PURE__ */ new Set(["strong", "b", "em", "i", "u", "s", "del", "code", "a", "br", "sup", "sub", "mark", "small", "abbr"]);
3875
+ function sanitizeInline(html) {
3876
+ 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) => {
3877
+ const t = tag.toLowerCase();
3878
+ if (!INLINE_TAGS.has(t)) return "";
3879
+ const name = t === "b" ? "strong" : t === "i" ? "em" : t;
3880
+ if (m.startsWith("</")) return `</${name}>`;
3881
+ if (t === "a") {
3882
+ const href = attrs.match(/href\s*=\s*("([^"]*)"|'([^']*)')/i);
3883
+ return `<a href="${escAttr(safeHref(href ? href[2] ?? href[3] ?? "" : ""))}">`;
3884
+ }
3885
+ if (t === "abbr") {
3886
+ const title = attrs.match(/title\s*=\s*"([^"]*)"/i);
3887
+ return title ? `<abbr title="${escAttr(title[1])}">` : "<abbr>";
3888
+ }
3889
+ return `<${name}>`;
3890
+ });
3891
+ }
3892
+ var alignOf = (col, t) => col.align ?? (t === "number" || t === "money" || t === "rating" ? "end" : "start");
3893
+ function orderedRows(t) {
3894
+ const groups = (t.rowGroups ?? []).filter(isObj);
3895
+ const known = new Set(groups.map((g) => g._key));
3896
+ const body = t.rows.filter((r) => isObj(r) && !r.footer);
3897
+ const loose = body.filter((r) => !r.group || !known.has(r.group));
3898
+ const bodies = [];
3899
+ if (loose.length || !groups.length) bodies.push({ rows: loose });
3900
+ for (const g of groups) {
3901
+ const rows = body.filter((r) => r.group === g._key);
3902
+ if (rows.length) bodies.push({ group: g, rows });
3903
+ }
3904
+ return { bodies, foot: t.rows.filter((r) => isObj(r) && r.footer) };
3905
+ }
3906
+ function cellHtml(t, col, row, c, fn, asHeader) {
3907
+ const v = tableCellValue(c);
3908
+ const type = tableCellType(col, row, c);
3909
+ const note = isTableCellObject(c) ? c.note : void 0;
3910
+ const n = note ? fn.get(note) : void 0;
3911
+ const sup = n ? `<sup><a href="#${escAttr(t.id ?? "tbl")}-fn-${n}" aria-label="Note ${n}">${n}</a></sup>` : "";
3912
+ let inner;
3913
+ if (isEmptyCell(v)) inner = asHeader ? "" : `<span aria-hidden="true" class="bt-dash">\u2014</span><span class="bt-sr">No value</span>`;
3914
+ else
3915
+ switch (type) {
3916
+ case "boolean": {
3917
+ const b = toBool(v);
3918
+ 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>`;
3919
+ break;
3920
+ }
3921
+ case "rating": {
3922
+ const num = toNumber(v);
3923
+ 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>`;
3924
+ break;
3925
+ }
3926
+ case "link": {
3927
+ const l = isObj(v) ? v : { label: String(v), href: "" };
3928
+ const rel = [l.newTab ? "noopener noreferrer" : "", l.rel && l.rel !== "follow" ? l.rel : ""].filter(Boolean).join(" ");
3929
+ 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 ?? "");
3930
+ break;
3931
+ }
3932
+ case "reference": {
3933
+ if (!isTableRef(v)) {
3934
+ inner = esc(strip(String(v)));
3935
+ break;
3936
+ }
3937
+ const href = refHref(v);
3938
+ inner = href ? `<a href="${escAttr(safeHref(href))}">${esc(refLabel(v))}</a>` : esc(refLabel(v));
3939
+ break;
3940
+ }
3941
+ case "richtext-inline":
3942
+ inner = sanitizeInline(String(isObj(v) ? v.label ?? "" : v));
3943
+ break;
3944
+ default:
3945
+ inner = esc(type === "number" || type === "money" ? tableCellText(col, row, c) : isObj(v) ? String(v.label ?? "") : strip(String(v)));
3946
+ }
3947
+ return { inner: inner + sup, align: alignOf(col, type) };
3948
+ }
3949
+ 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}';
3950
+ function renderTableHtml(input) {
3951
+ const t = readTable(input);
3952
+ const id = (t.id ?? "tbl").replace(/[^\w-]/g, "") || "tbl";
3953
+ const cols = t.columns.filter(isObj);
3954
+ const n = cols.length;
3955
+ if (!n) return "";
3956
+ const fn = new Map((t.footnotes ?? []).filter(isObj).map((f, i) => [f._key, i + 1]));
3957
+ const groups = (t.columnGroups ?? []).filter((g) => isObj(g) && cols.some((c) => c.group === g._key));
3958
+ const hasNotes = !!t.notes || fn.size > 0;
3959
+ const responsive = t.design?.responsive ?? "scroll";
3960
+ const fig = [];
3961
+ const attrs = [
3962
+ `class="bt-table"`,
3963
+ `data-responsive="${escAttr(responsive)}"`,
3964
+ t.design?.sticky ? `data-sticky="true"` : "",
3965
+ t.design?.striped ? `data-striped="true"` : "",
3966
+ t.design?.density === "compact" ? `data-density="compact"` : ""
3967
+ ].filter(Boolean);
3968
+ fig.push(`<figure ${attrs.join(" ")}><style>${TABLE_CSS}</style>`);
3969
+ fig.push(`<div class="bt-scroll" role="region" tabindex="0" aria-labelledby="${id}-caption">`);
3970
+ fig.push(`<table${hasNotes ? ` aria-describedby="${id}-notes"` : ""}>`);
3971
+ fig.push(`<caption id="${id}-caption"${t.design?.hideCaption ? ` class="bt-sr"` : ""}>${esc(t.caption || "Table")}</caption>`);
3972
+ if (t.headerRow) {
3973
+ fig.push("<thead>");
3974
+ if (groups.length) {
3975
+ const cells = [];
3976
+ for (let i = 0; i < n; ) {
3977
+ const g = cols[i].group ? groups.find((x) => x._key === cols[i].group) : void 0;
3978
+ if (!g) {
3979
+ cells.push("<td></td>");
3980
+ i++;
3981
+ continue;
3982
+ }
3983
+ let span = 1;
3984
+ while (i + span < n && cols[i + span].group === g._key) span++;
3985
+ cells.push(`<th scope="colgroup"${span > 1 ? ` colspan="${span}"` : ""}>${esc(g.label)}</th>`);
3986
+ i += span;
3987
+ }
3988
+ fig.push(`<tr>${cells.join("")}</tr>`);
3989
+ }
3990
+ fig.push(
3991
+ `<tr>${cols.map((c, i) => {
3992
+ if (i === 0 && t.headerColumn && !String(c.label ?? "").trim()) return "<td></td>";
3993
+ const align = alignOf(c, c.type);
3994
+ 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>`;
3995
+ }).join("")}</tr>`
3996
+ );
3997
+ fig.push("</thead>");
3998
+ }
3999
+ const rowHtml = (r) => {
4000
+ const cells = cols.map((c, i) => {
4001
+ const asTh = t.headerColumn && i === 0;
4002
+ const { inner, align } = cellHtml(t, c, r, isObj(r.cells) ? r.cells[c._key] : void 0, fn, asTh);
4003
+ const a = `${align !== "start" ? ` data-align="${align}"` : ""}${c.highlight ? ` data-highlight="true"` : ""}`;
4004
+ return asTh ? `<th scope="row"${a}>${inner}</th>` : `<td data-label="${escAttr(c.label || `Column ${i + 1}`)}"${a}>${inner}</td>`;
4005
+ });
4006
+ return `<tr${r.highlight ? ` data-highlight="true"` : ""}>${cells.join("")}</tr>`;
4007
+ };
4008
+ const { bodies, foot } = orderedRows(t);
4009
+ for (const b of bodies) {
4010
+ fig.push("<tbody>");
4011
+ if (b.group) fig.push(`<tr class="bt-group"><th scope="rowgroup" colspan="${n}">${esc(b.group.label)}</th></tr>`);
4012
+ fig.push(b.rows.map(rowHtml).join(""));
4013
+ fig.push("</tbody>");
4014
+ }
4015
+ if (foot.length) fig.push(`<tfoot>${foot.map(rowHtml).join("")}</tfoot>`);
4016
+ fig.push("</table></div>");
4017
+ if (hasNotes) {
4018
+ fig.push(`<div class="bt-notes" id="${id}-notes">`);
4019
+ if (t.notes) fig.push(`<p>${esc(t.notes)}</p>`);
4020
+ 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>`);
4021
+ fig.push("</div>");
4022
+ }
4023
+ fig.push("</figure>");
4024
+ const html = fig.join("");
4025
+ 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;
4026
+ }
4027
+ var mdSafe = (s) => s.replace(/(\]\([^)]*\))|[<>]/g, (m, link) => link ?? (m === "<" ? "&lt;" : "&gt;"));
4028
+ var mdCell = (s) => mdSafe(s).replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\s*\n\s*/g, " ").trim();
4029
+ var inlineMd = (html) => strip(
4030
+ 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, " ")
4031
+ );
4032
+ function tableToMarkdown(input) {
4033
+ const t = readTable(input);
4034
+ const cols = t.columns.filter(isObj);
4035
+ if (!cols.length) return "";
4036
+ const groups = new Map((t.columnGroups ?? []).filter(isObj).map((g) => [g._key, g.label]));
4037
+ const fn = new Map((t.footnotes ?? []).filter(isObj).map((f, i) => [f._key, i + 1]));
4038
+ const pfx = (t.id ?? "t").replace(/[^\w-]/g, "") || "t";
4039
+ const line = (cells) => `| ${cells.join(" | ")} |`;
4040
+ 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 ")) || " ");
4041
+ const sep = cols.map((c) => alignOf(c, c.type) === "end" ? "---:" : alignOf(c, c.type) === "center" ? ":---:" : "---");
4042
+ const row = (r) => line(
4043
+ cols.map((c, i) => {
4044
+ const cell = isObj(r.cells) ? r.cells[c._key] : void 0;
4045
+ const v = tableCellValue(cell);
4046
+ const ty = tableCellType(c, r, cell);
4047
+ let s = ty === "richtext-inline" && typeof v === "string" ? inlineMd(v) : tableCellText(c, r, cell);
4048
+ if (ty === "link" && isTableLink(v) && v.href && !isScriptHref(v.href)) s = `[${s}](${v.href})`;
4049
+ if (ty === "reference" && isTableRef(v)) {
4050
+ const href = refHref(v);
4051
+ if (href && !isScriptHref(href)) s = `[${s}](${href})`;
4052
+ }
4053
+ if (!s && !(t.headerColumn && i === 0)) s = "\u2014";
4054
+ if (t.headerColumn && i === 0 && s) s = `**${s}**`;
4055
+ const note = isTableCellObject(cell) ? cell.note : void 0;
4056
+ if (note && fn.get(note)) s += `[^${pfx}-${fn.get(note)}]`;
4057
+ return mdCell(s) || " ";
4058
+ })
4059
+ );
4060
+ const out = [`**${mdCell(t.caption || "Table")}**`, "", line(head), line(sep)];
4061
+ const { bodies, foot } = orderedRows(t);
4062
+ for (const b of bodies) {
4063
+ if (b.group) out.push(line([`**${mdCell(b.group.label)}**`, ...cols.slice(1).map(() => " ")]));
4064
+ b.rows.forEach((r) => out.push(row(r)));
4065
+ }
4066
+ foot.forEach((r) => out.push(row(r)));
4067
+ const after = [];
4068
+ if (t.notes) after.push(mdSafe(t.notes));
4069
+ const described = cols.filter((c) => c.description);
4070
+ if (described.length) after.push(mdSafe(described.map((c) => `${c.label}: ${c.description}`).join(". ") + "."));
4071
+ if (fn.size) after.push((t.footnotes ?? []).filter(isObj).map((f, i) => `[^${pfx}-${i + 1}]: ${mdSafe(f.text)}`).join("\n"));
4072
+ return [out.join("\n"), ...after].join("\n\n");
4073
+ }
4074
+ function tableSearchText(input) {
4075
+ if (!isTableValue(input)) return "";
4076
+ const t = input;
4077
+ const cols = t.columns.filter(isObj);
4078
+ const parts = [t.caption, t.notes ?? ""];
4079
+ for (const c of cols) parts.push(c.label, c.description ?? "");
4080
+ for (const g of [...t.columnGroups ?? [], ...t.rowGroups ?? []]) if (isObj(g)) parts.push(g.label);
4081
+ for (const r of t.rows) {
4082
+ if (!isObj(r) || !isObj(r.cells)) continue;
4083
+ for (const c of cols) {
4084
+ const cell = r.cells[c._key];
4085
+ parts.push(tableCellType(c, r, cell) === "richtext-inline" ? strip(String(tableCellValue(cell) ?? "")) : tableCellText(c, r, cell));
4086
+ }
4087
+ }
4088
+ for (const f of t.footnotes ?? []) if (isObj(f)) parts.push(f.text);
4089
+ return parts.filter((p) => typeof p === "string" && p.trim() !== "").map((p) => p.trim()).join(" ");
4090
+ }
4091
+
3158
4092
  // src/content-model.ts
3159
4093
  var OPTION_DESCRIPTION_MAX = 140;
3160
4094
 
@@ -3218,15 +4152,108 @@ function regionOf(regions, el) {
3218
4152
  for (const r of regions) if (r.el.contains(el)) slot = r.slot;
3219
4153
  return slot;
3220
4154
  }
4155
+
4156
+ // src/sections.ts
4157
+ var TAG_HEADS = /* @__PURE__ */ new Set([
4158
+ "h1",
4159
+ "h2",
4160
+ "h3",
4161
+ "h4",
4162
+ "h5",
4163
+ "h6",
4164
+ "p",
4165
+ "a",
4166
+ "img",
4167
+ "li",
4168
+ "list",
4169
+ "blockquote",
4170
+ "figcaption",
4171
+ "button",
4172
+ "summary",
4173
+ "dt",
4174
+ "dd",
4175
+ "th",
4176
+ "td",
4177
+ "pre",
4178
+ "label",
4179
+ "span",
4180
+ "div",
4181
+ "time",
4182
+ "strong",
4183
+ "em",
4184
+ "small",
4185
+ "b",
4186
+ "i",
4187
+ "code",
4188
+ "icon",
4189
+ "bg"
4190
+ ]);
4191
+ var topFieldKeyOf = (path) => path.split(/[.[]/, 1)[0] ?? path;
4192
+ function fieldGroupOf(topKey, flat = false) {
4193
+ if (topKey.startsWith("group-")) return { groupKey: topKey, rooted: true };
4194
+ const camel = flat ? /^([a-z][a-z0-9]*)[A-Z]/.exec(topKey)?.[1] : void 0;
4195
+ if (camel) return TAG_HEADS.has(camel) ? { groupKey: topKey, rooted: false } : { groupKey: camel, rooted: true };
4196
+ const head = topKey.split(/[-_]/, 1)[0] || topKey;
4197
+ if (TAG_HEADS.has(head)) return { groupKey: topKey, rooted: false };
4198
+ return { groupKey: head, rooted: true };
4199
+ }
4200
+ function placementBindOf(block) {
4201
+ if (!block || typeof block !== "object" || block.type !== "component") return null;
4202
+ const props = block.props;
4203
+ const bind = props?.bind;
4204
+ if (typeof bind === "string" && bind) return bind;
4205
+ const source = props?.source;
4206
+ if (!source || typeof source !== "object") return null;
4207
+ 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 }));
4208
+ return (groups.find((g) => g.group !== g.top) ?? groups[0])?.group ?? null;
4209
+ }
4210
+ var CODE_SLOT_PREFIX = "code:";
4211
+ var slotBindOf = (slot) => slot.startsWith(CODE_SLOT_PREFIX) ? slot.slice(CODE_SLOT_PREFIX.length) : slot;
4212
+ function planSections(slots, blocks) {
4213
+ if (!Array.isArray(blocks)) return slots.map((slot) => ({ slot, block: null }));
4214
+ const byBind = /* @__PURE__ */ new Map();
4215
+ for (const slot of slots) if (!byBind.has(slotBindOf(slot))) byBind.set(slotBindOf(slot), slot);
4216
+ const out = [];
4217
+ const named = /* @__PURE__ */ new Set();
4218
+ for (const block of blocks) {
4219
+ const bind = placementBindOf(block);
4220
+ if (bind === null) {
4221
+ out.push({ slot: null, block });
4222
+ continue;
4223
+ }
4224
+ const slot = byBind.get(bind);
4225
+ if (slot === void 0 || named.has(slot)) continue;
4226
+ named.add(slot);
4227
+ out.push({ slot, block });
4228
+ }
4229
+ const held = named.size > 0;
4230
+ slots.forEach((slot, n) => {
4231
+ if (named.has(slot) || out.some((item) => item.slot === slot)) return;
4232
+ if (held && !slot.startsWith(CODE_SLOT_PREFIX)) return;
4233
+ let at = 0;
4234
+ for (let before = n - 1; before >= 0; before--) {
4235
+ const found = out.findIndex((item) => item.slot === slots[before]);
4236
+ if (found >= 0) {
4237
+ at = found + 1;
4238
+ break;
4239
+ }
4240
+ }
4241
+ out.splice(at, 0, { slot, block: null });
4242
+ });
4243
+ return out;
4244
+ }
3221
4245
  export {
3222
4246
  BLOCK_BINDINGS,
3223
4247
  BREAKPOINTS,
4248
+ BUTTON_ACTIONS,
4249
+ BUTTON_FULL_WIDTH_INVALID,
3224
4250
  BUTTON_ICONS,
3225
4251
  BUTTON_ICON_NAMES,
3226
4252
  BUTTON_LOOK_KEYS,
3227
4253
  BUTTON_PLACEMENT_KEYS,
3228
4254
  BUTTON_SIZES,
3229
4255
  BUTTON_VARIANTS,
4256
+ CODE_SLOT_PREFIX,
3230
4257
  COMPONENT_PROP_TYPES,
3231
4258
  DIALOG_SELECTOR,
3232
4259
  DOCK_BUDGET,
@@ -3247,15 +4274,24 @@ export {
3247
4274
  RESPONSIVE_CSS,
3248
4275
  SECTION_DOCTRINE,
3249
4276
  SECTION_SPACE_TOKENS,
4277
+ SLOT_CHILD_KEYS,
3250
4278
  SLOT_ITEMS_CAP,
3251
4279
  SLOT_MAX_ITEMS,
3252
4280
  SPACE_BOTTOM_TOKENS,
3253
4281
  SPACE_STEP_TOKENS,
4282
+ TABLE_COLUMN_TYPES,
4283
+ TABLE_JSONLD_TYPES,
4284
+ TABLE_LIMITS,
4285
+ TABLE_RESPONSIVE,
4286
+ TAG_HEADS,
4287
+ VIDEO_TRANSCRIPT_MAX,
4288
+ VIDEO_VALUE_KEYS,
3254
4289
  anchorTargetRel,
3255
4290
  blockStyleToColumnCss,
3256
4291
  blockStyleToCss,
3257
4292
  blockStyleToInnerCss,
3258
4293
  buttonBlockProps,
4294
+ buttonFullWidthProblem,
3259
4295
  buttonIconOf,
3260
4296
  buttonIconSvg,
3261
4297
  buttonLinkPairs,
@@ -3271,8 +4307,10 @@ export {
3271
4307
  dockBudget,
3272
4308
  dockScreens,
3273
4309
  expandBlockBindings,
4310
+ fieldGroupOf,
3274
4311
  fieldStyleCssText,
3275
4312
  fieldStyleToCss,
4313
+ formatNumber,
3276
4314
  formatPropsAttribute,
3277
4315
  getBlockType,
3278
4316
  headingPaintCss,
@@ -3281,8 +4319,11 @@ export {
3281
4319
  isBlock,
3282
4320
  isButtonLookValue,
3283
4321
  isChromeLandmark,
4322
+ isCodeButton,
3284
4323
  isLinkObject,
3285
4324
  isScriptHref,
4325
+ isSlotChild,
4326
+ isTableValue,
3286
4327
  keyWords,
3287
4328
  landmarkRegions,
3288
4329
  linkAnchorOf,
@@ -3291,17 +4332,36 @@ export {
3291
4332
  linkValueProblem,
3292
4333
  locatorFor,
3293
4334
  normalizeLinkValue,
4335
+ normalizeTableValue,
3294
4336
  normalizeWindow,
4337
+ parseNumberText,
3295
4338
  parsePropsAttribute,
3296
4339
  pickFieldStyle,
4340
+ placementBindOf,
4341
+ planSections,
3297
4342
  readButtonValue,
4343
+ readNumberFormat,
3298
4344
  readPath,
3299
4345
  regionOf,
4346
+ renderTableHtml,
3300
4347
  safeCssUrl,
4348
+ slotBindOf,
4349
+ slotChildValues,
3301
4350
  slotItems,
4351
+ slotPathString,
4352
+ slotShapeProblems,
3302
4353
  spaceTokenCss,
3303
4354
  splitButtonStyle,
4355
+ tableCellText,
4356
+ tablePathString,
4357
+ tableRefCells,
4358
+ tableSearchText,
4359
+ tableToMarkdown,
4360
+ tableValueProblems,
4361
+ topFieldKeyOf,
3304
4362
  usesHideOn,
4363
+ videoUrlOf,
4364
+ videoValueProblem,
3305
4365
  visibleBlocks,
3306
4366
  visibleDeliveredLayout,
3307
4367
  visibleLayoutStructure,