@vectoriox/iox-ui 5.37.0 → 5.39.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.
Files changed (45) hide show
  1. package/actions-core/index.d.ts +6 -1
  2. package/composite-core/index.d.ts +684 -28
  3. package/design-core/index.d.ts +452 -6
  4. package/fesm2022/vectoriox-iox-ui-actions-core.mjs +12 -4
  5. package/fesm2022/vectoriox-iox-ui-actions-core.mjs.map +1 -1
  6. package/fesm2022/vectoriox-iox-ui-composite-core.mjs +1761 -143
  7. package/fesm2022/vectoriox-iox-ui-composite-core.mjs.map +1 -1
  8. package/fesm2022/vectoriox-iox-ui-design-core.mjs +555 -6
  9. package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
  10. package/fesm2022/vectoriox-iox-ui-generation-core.mjs +534 -12
  11. package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
  12. package/fesm2022/vectoriox-iox-ui-media-core.mjs +571 -0
  13. package/fesm2022/vectoriox-iox-ui-media-core.mjs.map +1 -0
  14. package/fesm2022/vectoriox-iox-ui.mjs +494 -127
  15. package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
  16. package/generation-core/index.d.ts +210 -4
  17. package/index.d.ts +217 -30
  18. package/media-core/index.d.ts +277 -0
  19. package/media-core/package.json +3 -0
  20. package/package.json +5 -1
  21. package/src/actions-core/element-actions.ts +12 -3
  22. package/src/composite-core/accordion-runtime.ts +3 -15
  23. package/src/composite-core/accordion.ts +4 -2
  24. package/src/composite-core/composite-kinds.ts +121 -7
  25. package/src/composite-core/composite-node.ts +4 -0
  26. package/src/composite-core/composite-runtime.ts +68 -0
  27. package/src/composite-core/exclusive-runtime.ts +358 -0
  28. package/src/composite-core/lightbox.ts +192 -0
  29. package/src/composite-core/public-api.ts +8 -2
  30. package/src/composite-core/slider-runtime.ts +180 -0
  31. package/src/composite-core/slider.ts +378 -0
  32. package/src/composite-core/structure.ts +38 -5
  33. package/src/composite-core/tabs.ts +628 -0
  34. package/src/design-core/contrast-audit.ts +5 -0
  35. package/src/design-core/element-styles.ts +34 -3
  36. package/src/design-core/flavor-spec.generated.ts +517 -2
  37. package/src/generation-core/baseline-roles.ts +2 -0
  38. package/src/generation-core/compose-site.ts +147 -4
  39. package/src/generation-core/node-baselines.generated.ts +156 -6
  40. package/src/generation-core/patterns.ts +20 -0
  41. package/src/generation-core/site-spec.generated.ts +210 -0
  42. package/src/media-core/public-api.ts +9 -0
  43. package/src/media-core/video-runtime.ts +239 -0
  44. package/src/media-core/video.ts +443 -0
  45. package/styles/iox-design.css +112 -0
@@ -1,6 +1,6 @@
1
1
  import { NEW_NODE_STYLE_DEFAULTS, defaultElementStyleFor, elementStyleById, ownedTraitsOf, applyElementStyle, flavorPackById, parseHexColor, SPEC_AXES, LinkType, resolveDesignChoices } from '@vectoriox/iox-ui/design-core';
2
2
  import { formPresetById } from '@vectoriox/iox-ui/forms-core';
3
- import { CardRole, CardLayout, CARD_LAYOUTS, CARD_PARTS, buildCardRecipe, findByRole, AccordionRole, buildAccordionRecipe, ACCORDION_WORDS, accordionItems, accordionQuestionOf, accordionAnswerOf, ACCORDION_BIND_PROP, pickAccordionAutoBinds, ACCORDION_CONTENT_LIMIT } from '@vectoriox/iox-ui/composite-core';
3
+ import { CardRole, CardLayout, CARD_LAYOUTS, CARD_PARTS, buildCardRecipe, findByRole, AccordionRole, buildAccordionRecipe, ACCORDION_WORDS, accordionItems, accordionQuestionOf, accordionAnswerOf, ACCORDION_BIND_PROP, pickAccordionAutoBinds, ACCORDION_CONTENT_LIMIT, buildTabsRecipe, TABS_WORDS, tabsTriggers, tabLabelNodeOf, TABS_LABEL_BIND_PROP, tabsPanels, tabPanelTextOf, TABS_TEXT_BIND_PROP, tabPanelImageOf, pickTabsAutoBinds, tabsListOf, tabsPanelsOf, TABS_CONTENT_LIMIT, TABS_IMAGE_BIND_PROP, buildCarouselRecipe, SLIDE_TYPE, buildLightboxRecipe, LIGHTBOX_LABEL, SLIDE_TO_ROW } from '@vectoriox/iox-ui/composite-core';
4
4
 
5
5
  // GENERATED from iox-ai-guidance flavor-spec.v1.json — do not edit; re-run scripts/gen-flavor-spec.mjs
6
6
  // (npm run gen:flavor-spec). The JSON is itself generated by iox-ai-guidance
@@ -980,6 +980,216 @@ const SPEC_SITE_PATTERNS = {
980
980
  }
981
981
  ]
982
982
  }
983
+ },
984
+ "feature.tabbed": {
985
+ "family": "feature",
986
+ "label": "Features — tabs that switch",
987
+ "node": {
988
+ "t": "Section",
989
+ "es": "section",
990
+ "c": [
991
+ {
992
+ "t": "Container",
993
+ "es": "box",
994
+ "lay": {
995
+ "display": "flex",
996
+ "flexDirection": "column",
997
+ "gap": "14px",
998
+ "maxWidth": "1100px",
999
+ "width": "100%",
1000
+ "margin": "0px auto 0px auto"
1001
+ },
1002
+ "c": [
1003
+ {
1004
+ "t": "Text",
1005
+ "es": "eyebrow",
1006
+ "slot": "eyebrow",
1007
+ "ph": "copy",
1008
+ "text": "What we offer"
1009
+ },
1010
+ {
1011
+ "t": "Heading",
1012
+ "es": "h2",
1013
+ "level": "h2",
1014
+ "slot": "title",
1015
+ "ph": "copy",
1016
+ "text": "What we design"
1017
+ },
1018
+ {
1019
+ "t": "Container",
1020
+ "recipe": "tabs",
1021
+ "src": "$binding?",
1022
+ "ph": "copy",
1023
+ "name": "What we design"
1024
+ }
1025
+ ]
1026
+ }
1027
+ ]
1028
+ }
1029
+ },
1030
+ "hero.full-bleed-media": {
1031
+ "family": "hero",
1032
+ "label": "Hero — words over a background video",
1033
+ "node": {
1034
+ "t": "Section",
1035
+ "es": "section",
1036
+ "scheme": "inverse",
1037
+ "lay": {
1038
+ "position": "relative",
1039
+ "overflow": "hidden",
1040
+ "display": "flex",
1041
+ "flexDirection": "column",
1042
+ "justifyContent": "flex-end",
1043
+ "minHeight": "70vh"
1044
+ },
1045
+ "c": [
1046
+ {
1047
+ "t": "Video",
1048
+ "es": "media",
1049
+ "video": "background",
1050
+ "ph": "video",
1051
+ "lay": {
1052
+ "position": "absolute",
1053
+ "top": "0px",
1054
+ "left": "0px",
1055
+ "width": "100%",
1056
+ "height": "100%",
1057
+ "aspectRatio": "auto",
1058
+ "borderRadius": "0px",
1059
+ "zIndex": "0"
1060
+ }
1061
+ },
1062
+ {
1063
+ "t": "Container",
1064
+ "es": "media-scrim",
1065
+ "lay": {
1066
+ "display": "flex",
1067
+ "flexDirection": "column",
1068
+ "gap": "14px",
1069
+ "position": "relative",
1070
+ "zIndex": "1",
1071
+ "padding": "32px 32px 32px 32px",
1072
+ "maxWidth": "760px"
1073
+ },
1074
+ "c": [
1075
+ {
1076
+ "t": "Heading",
1077
+ "es": "display",
1078
+ "level": "h1",
1079
+ "slot": "title",
1080
+ "ph": "copy",
1081
+ "text": "Rooms that hold the light"
1082
+ },
1083
+ {
1084
+ "t": "Text",
1085
+ "es": "lead",
1086
+ "slot": "lead",
1087
+ "fromBusiness": "tagline",
1088
+ "ph": "copy"
1089
+ },
1090
+ {
1091
+ "t": "ButtonBlock",
1092
+ "es": "button-primary",
1093
+ "href": "/contact",
1094
+ "slot": "cta",
1095
+ "text": "Get in touch"
1096
+ }
1097
+ ]
1098
+ }
1099
+ ]
1100
+ }
1101
+ },
1102
+ "proof.testimonial-carousel": {
1103
+ "family": "proof",
1104
+ "label": "Testimonials that rotate",
1105
+ "node": {
1106
+ "t": "Section",
1107
+ "es": "section",
1108
+ "c": [
1109
+ {
1110
+ "t": "Container",
1111
+ "es": "box",
1112
+ "lay": {
1113
+ "display": "flex",
1114
+ "flexDirection": "column",
1115
+ "gap": "14px",
1116
+ "maxWidth": "860px",
1117
+ "width": "100%",
1118
+ "margin": "0px auto 0px auto"
1119
+ },
1120
+ "c": [
1121
+ {
1122
+ "t": "Text",
1123
+ "es": "eyebrow",
1124
+ "slot": "eyebrow",
1125
+ "ph": "copy",
1126
+ "text": "What clients say"
1127
+ },
1128
+ {
1129
+ "t": "SliderContainer",
1130
+ "recipe": "carousel",
1131
+ "ph": "copy"
1132
+ }
1133
+ ]
1134
+ }
1135
+ ]
1136
+ }
1137
+ },
1138
+ "collection.masonry-gallery": {
1139
+ "family": "collection",
1140
+ "label": "Gallery that opens full screen",
1141
+ "node": {
1142
+ "t": "Section",
1143
+ "es": "section",
1144
+ "c": [
1145
+ {
1146
+ "t": "Repeater",
1147
+ "src": "route.gallery",
1148
+ "lay": {
1149
+ "display": "grid",
1150
+ "gridTemplateColumns": "repeat(3, minmax(0, 1fr))",
1151
+ "gap": "var(--iox-space-gap)"
1152
+ },
1153
+ "resp": {
1154
+ "mobile": {
1155
+ "gridTemplateColumns": "repeat(2, minmax(0, 1fr))"
1156
+ }
1157
+ },
1158
+ "c": [
1159
+ {
1160
+ "t": "ButtonBlock",
1161
+ "es": "button-ghost",
1162
+ "opens": "lightbox",
1163
+ "name": "Open the image",
1164
+ "lay": {
1165
+ "display": "block",
1166
+ "width": "100%",
1167
+ "padding": "0px 0px 0px 0px"
1168
+ },
1169
+ "c": [
1170
+ {
1171
+ "t": "Image",
1172
+ "es": "media",
1173
+ "bind": "item",
1174
+ "alt": "",
1175
+ "aspect": "4 / 3"
1176
+ }
1177
+ ]
1178
+ }
1179
+ ]
1180
+ },
1181
+ {
1182
+ "t": "Dialog",
1183
+ "recipe": "lightbox",
1184
+ "src": "route.gallery"
1185
+ }
1186
+ ],
1187
+ "lay": {
1188
+ "display": "flex",
1189
+ "flexDirection": "column",
1190
+ "gap": "32px"
1191
+ }
1192
+ }
983
1193
  }
984
1194
  };
985
1195
  const SPEC_SITE_TEMPLATES = {
@@ -1865,7 +2075,10 @@ const NODE_BASELINES = {
1865
2075
  "htmlTag",
1866
2076
  "colorScheme",
1867
2077
  "multiple",
1868
- "defaultOn"
2078
+ "defaultOn",
2079
+ "activateOn",
2080
+ "orientation",
2081
+ "ariaLabel"
1869
2082
  ],
1870
2083
  "styleTraits": [
1871
2084
  "position",
@@ -4098,7 +4311,10 @@ const NODE_BASELINES = {
4098
4311
  "source",
4099
4312
  "limit",
4100
4313
  "multiple",
4101
- "defaultOn"
4314
+ "defaultOn",
4315
+ "activateOn",
4316
+ "orientation",
4317
+ "ariaLabel"
4102
4318
  ],
4103
4319
  "styleTraits": [
4104
4320
  "position",
@@ -4230,7 +4446,8 @@ const NODE_BASELINES = {
4230
4446
  "enableParallax": false,
4231
4447
  "enableGrid": false,
4232
4448
  "gridRows": 2,
4233
- "breakpoints": ""
4449
+ "breakpoints": "",
4450
+ "ariaLabel": ""
4234
4451
  },
4235
4452
  "styleProps": {
4236
4453
  "position": "static",
@@ -4340,7 +4557,9 @@ const NODE_BASELINES = {
4340
4557
  "enableGrid",
4341
4558
  "gridRows",
4342
4559
  "__responsiveLabel",
4343
- "breakpoints"
4560
+ "breakpoints",
4561
+ "__accessibilityLabel",
4562
+ "ariaLabel"
4344
4563
  ],
4345
4564
  "styleTraits": [
4346
4565
  "position",
@@ -5858,7 +6077,8 @@ const NODE_BASELINES = {
5858
6077
  "showCloseButton": true,
5859
6078
  "consentRole": false,
5860
6079
  "closeAbove": "",
5861
- "ariaLabel": ""
6080
+ "ariaLabel": "",
6081
+ "colorScheme": "default"
5862
6082
  },
5863
6083
  "styleProps": {
5864
6084
  "position": "static",
@@ -5951,7 +6171,8 @@ const NODE_BASELINES = {
5951
6171
  "__responsiveLabel",
5952
6172
  "closeAbove",
5953
6173
  "__accessibilityLabel",
5954
- "ariaLabel"
6174
+ "ariaLabel",
6175
+ "colorScheme"
5955
6176
  ],
5956
6177
  "styleTraits": [
5957
6178
  "position",
@@ -6045,6 +6266,145 @@ const NODE_BASELINES = {
6045
6266
  "transitionTimingFunction"
6046
6267
  ],
6047
6268
  "container": true
6269
+ },
6270
+ "Video": {
6271
+ "props": {
6272
+ "videoSource": "youtube",
6273
+ "src": "",
6274
+ "poster": "",
6275
+ "mode": "inline",
6276
+ "autoplay": false,
6277
+ "title": "",
6278
+ "captions": "",
6279
+ "captionsLang": "en",
6280
+ "consentCategory": "marketing"
6281
+ },
6282
+ "styleProps": {
6283
+ "position": "static",
6284
+ "top": "auto",
6285
+ "right": "auto",
6286
+ "bottom": "auto",
6287
+ "left": "auto",
6288
+ "zIndex": "0",
6289
+ "width": "100%",
6290
+ "height": "auto",
6291
+ "minWidth": "auto",
6292
+ "minHeight": "auto",
6293
+ "maxWidth": "auto",
6294
+ "maxHeight": "auto",
6295
+ "aspectRatio": "16 / 9",
6296
+ "objectFit": "cover",
6297
+ "overflow": "visible",
6298
+ "margin": "0px 0px 0px 0px",
6299
+ "padding": "0px 0px 0px 0px",
6300
+ "borderWidth": "0px",
6301
+ "borderStyle": "solid",
6302
+ "borderColor": "#CCCCCC",
6303
+ "borderRadius": "0px",
6304
+ "opacity": "1",
6305
+ "mixBlendMode": "normal",
6306
+ "cursor": "auto",
6307
+ "boxShadow": "",
6308
+ "textShadow": "",
6309
+ "translateX": "0px",
6310
+ "translateY": "0px",
6311
+ "scaleX": "1",
6312
+ "scaleY": "1",
6313
+ "rotate": "0deg",
6314
+ "skewX": "0deg",
6315
+ "skewY": "0deg",
6316
+ "filterBlur": "0px",
6317
+ "filterHueRotate": "0deg",
6318
+ "filterBrightness": "1",
6319
+ "filterContrast": "1",
6320
+ "filterSaturate": "1",
6321
+ "filterGrayscale": "0",
6322
+ "filterSepia": "0",
6323
+ "filterInvert": "0",
6324
+ "backdropFilter": "none",
6325
+ "backdropBlur": "0px",
6326
+ "backdropBrightness": "1",
6327
+ "backdropContrast": "1",
6328
+ "backdropSaturate": "1",
6329
+ "clipPath": "none",
6330
+ "transitionDuration": "0ms",
6331
+ "transitionDelay": "0ms",
6332
+ "transitionTimingFunction": "ease"
6333
+ },
6334
+ "traits": [
6335
+ "__mediaLabel",
6336
+ "videoSource",
6337
+ "src",
6338
+ "poster",
6339
+ "__behaviourLabel",
6340
+ "mode",
6341
+ "autoplay",
6342
+ "__accessibilityLabel",
6343
+ "title",
6344
+ "captions",
6345
+ "captionsLang",
6346
+ "__privacyLabel",
6347
+ "consentCategory"
6348
+ ],
6349
+ "styleTraits": [
6350
+ "position",
6351
+ "top",
6352
+ "right",
6353
+ "bottom",
6354
+ "left",
6355
+ "zIndex",
6356
+ "width",
6357
+ "height",
6358
+ "minWidth",
6359
+ "minHeight",
6360
+ "maxWidth",
6361
+ "maxHeight",
6362
+ "aspectRatio",
6363
+ "objectFit",
6364
+ "overflow",
6365
+ "margin",
6366
+ "padding",
6367
+ "borderWidth",
6368
+ "borderStyle",
6369
+ "borderColor",
6370
+ "borderRadius",
6371
+ "opacity",
6372
+ "mixBlendMode",
6373
+ "cursor",
6374
+ "_sep_shadow",
6375
+ "boxShadow",
6376
+ "textShadow",
6377
+ "_sep_transform",
6378
+ "translateX",
6379
+ "translateY",
6380
+ "scaleX",
6381
+ "scaleY",
6382
+ "rotate",
6383
+ "skewX",
6384
+ "skewY",
6385
+ "_sep_filter",
6386
+ "filterBlur",
6387
+ "filterHueRotate",
6388
+ "filterBrightness",
6389
+ "filterContrast",
6390
+ "filterSaturate",
6391
+ "filterGrayscale",
6392
+ "filterSepia",
6393
+ "filterInvert",
6394
+ "_sep_backdrop",
6395
+ "backdropFilter",
6396
+ "backdropBlur",
6397
+ "backdropBrightness",
6398
+ "backdropContrast",
6399
+ "backdropSaturate",
6400
+ "_sep_clip",
6401
+ "clipPath",
6402
+ "_sep_transition",
6403
+ "transitionDuration",
6404
+ "transitionDelay",
6405
+ "transitionTimingFunction"
6406
+ ],
6407
+ "container": false
6048
6408
  }
6049
6409
  };
6050
6410
  /** What every style trait saves as when no constructor sets it (buildFullStyleTraits' defaults). */
@@ -6192,6 +6552,8 @@ const BASELINE_ROLES = {
6192
6552
  FormSuccess: { width: Behaviour, color: Look, padding: Look, borderRadius: Look, backgroundColor: Look },
6193
6553
  FormError: { width: Behaviour, color: Look, padding: Look, borderRadius: Look, backgroundColor: Look },
6194
6554
  Dialog: { width: Behaviour, padding: Look, borderRadius: Look, backgroundColor: Look, boxShadow: Look },
6555
+ // CC3: a Video fills its column at 16 / 9 — the shape the poster and the player take.
6556
+ Video: { width: Behaviour, aspectRatio: Behaviour },
6195
6557
  };
6196
6558
  /** The style values a type's constructor sets for an empty palette drop, each mapped to the value the
6197
6559
  * trait saves when nobody sets it — what a composed node of that type starts from. */
@@ -6249,6 +6611,13 @@ var PatternRecipe;
6249
6611
  /** CC2: the Accordion — Manual (three placeholder items), or From content when the section names a list
6250
6612
  * `binding` (`src: '$binding?'`): one template item bound to the type's question / answer fields. */
6251
6613
  PatternRecipe["Accordion"] = "accordion";
6614
+ /** CC4a: Tabs — Manual (three placeholder tabs), or From content when the section names a list `binding`: the tab
6615
+ * list and the panels are both Repeaters over it, one row giving its tab AND its panel. */
6616
+ PatternRecipe["Tabs"] = "tabs";
6617
+ /** CC3: the accessible carousel — a named Slider whose controls are parts, one quote per slide (placeholders). */
6618
+ PatternRecipe["Carousel"] = "carousel";
6619
+ /** CC3: the Lightbox — a Dialog holding a Slider over the node's `src` (the gallery's own source). */
6620
+ PatternRecipe["Lightbox"] = "lightbox";
6252
6621
  })(PatternRecipe || (PatternRecipe = {}));
6253
6622
  /** A recipe node's `binds` keys → the part each binds (Card v2). */
6254
6623
  var CardBindTarget;
@@ -6273,7 +6642,14 @@ var PatternPlaceholder;
6273
6642
  (function (PatternPlaceholder) {
6274
6643
  PatternPlaceholder["Copy"] = "copy";
6275
6644
  PatternPlaceholder["Image"] = "image";
6645
+ /** CC3: a clip the studio uploads (a Video's file): the poster blank until then, publish-blocking. */
6646
+ PatternPlaceholder["Video"] = "video";
6276
6647
  })(PatternPlaceholder || (PatternPlaceholder = {}));
6648
+ /** CC3: what an `opens` node opens. */
6649
+ var PatternOpens;
6650
+ (function (PatternOpens) {
6651
+ PatternOpens["Lightbox"] = "lightbox";
6652
+ })(PatternOpens || (PatternOpens = {}));
6277
6653
  /** The composer directives a pattern writes where a plan supplies the value. */
6278
6654
  var PatternDirective;
6279
6655
  (function (PatternDirective) {
@@ -7004,7 +7380,7 @@ const BAR_ITEMS_MAX = 5;
7004
7380
  class NavShapeError extends Error {
7005
7381
  }
7006
7382
  const CONTAINER = 'Container';
7007
- const DIALOG = 'Dialog';
7383
+ const DIALOG$1 = 'Dialog';
7008
7384
  const LINK = 'Link';
7009
7385
  const LINKED = 'LinkedContainer';
7010
7386
  const BUTTON = 'ButtonBlock';
@@ -7113,7 +7489,7 @@ function navPages(root) {
7113
7489
  function collectHeaderLinks(root) {
7114
7490
  const out = [];
7115
7491
  const visit = (n) => {
7116
- if (n.type === DIALOG)
7492
+ if (n.type === DIALOG$1)
7117
7493
  return;
7118
7494
  if (n.type === LINK)
7119
7495
  out.push(n);
@@ -7200,7 +7576,7 @@ function pageLink(kit, suffix, page, role, lay = {}) {
7200
7576
  return part(kit, suffix, LINK, { label: page.label, linkType: LinkType.Page, pageRoute: page.route }, 'link', role, lay);
7201
7577
  }
7202
7578
  function drawer(kit, root, pages, at) {
7203
- const dr = part(kit, 'drawer', DIALOG, {
7579
+ const dr = part(kit, 'drawer', DIALOG$1, {
7204
7580
  placement: 'end', closeOnBackdrop: true, showCloseButton: false, ariaLabel: NAV_SHAPE_COPY.menu, closeAbove: at,
7205
7581
  }, 'dialog', NavRole.Drawer, { width: '86vw', maxWidth: '360px', display: 'flex', flexDirection: 'column', gap: '20px' });
7206
7582
  const brandWords = textOf(find([root], NavRole.Brand) ?? root) || NAV_PART_LABELS[NavRole.Root];
@@ -7484,6 +7860,8 @@ var PlaceholderKind;
7484
7860
  PlaceholderKind["Copy"] = "copy";
7485
7861
  /** An image slot with no image: the empty-image placeholder (DS3). */
7486
7862
  PlaceholderKind["Image"] = "image";
7863
+ /** CC3: a video slot with no clip — the studio uploads its own (the poster blank meanwhile). */
7864
+ PlaceholderKind["Video"] = "video";
7487
7865
  })(PlaceholderKind || (PlaceholderKind = {}));
7488
7866
  var ComposeIssueCode;
7489
7867
  (function (ComposeIssueCode) {
@@ -7541,6 +7919,10 @@ const BUTTON_BLOCK = 'ButtonBlock';
7541
7919
  const FORM = 'Form';
7542
7920
  const FIELD = 'Field';
7543
7921
  const IMAGE = 'Image';
7922
+ const VIDEO = 'Video';
7923
+ /** A Video's source prop (media-core `VideoTrait.Source` — not `source`, which names a data source). */
7924
+ const VIDEO_SOURCE_PROP = 'videoSource';
7925
+ const DIALOG = 'Dialog';
7544
7926
  const SUBMIT_BUTTON = 'SubmitButton';
7545
7927
  const FORM_SUCCESS = 'FormSuccess';
7546
7928
  const FORM_ERROR = 'FormError';
@@ -7827,6 +8209,24 @@ function composeNode(pn, id, repeater, ctx) {
7827
8209
  setProp(node, prop, words.value);
7828
8210
  }
7829
8211
  }
8212
+ // CC3: a Video in background mode is an uploaded file's; its clip is the studio's own (a placeholder until then).
8213
+ if (pn.video) {
8214
+ if (type !== VIDEO)
8215
+ throw new ComposeError(`${id}: "video" belongs on a Video`);
8216
+ setProp(node, VIDEO_SOURCE_PROP, 'file');
8217
+ setProp(node, 'mode', pn.video);
8218
+ }
8219
+ if (pn.ph === PatternPlaceholder.Video) {
8220
+ setProp(node, 'src', '');
8221
+ ctx.placeholders.push({ nodeId: id, owner: ctx.owner, kind: PlaceholderKind.Video, text: '' });
8222
+ }
8223
+ if (pn.name)
8224
+ setProp(node, 'ariaLabel', pn.name);
8225
+ if (pn.opens) {
8226
+ if (pn.opens !== PatternOpens.Lightbox)
8227
+ throw new ComposeError(`${id}: "opens" names the pattern's lightbox`);
8228
+ (ctx.openers ??= []).push(id);
8229
+ }
7830
8230
  // Images: an empty slot, or a bound one — both fall back to the DS3 empty image, never a stock URL.
7831
8231
  if (pn.ph === PatternPlaceholder.Image) {
7832
8232
  setProp(node, 'src', '');
@@ -7921,6 +8321,12 @@ const CARD_BINDS = {
7921
8321
  function composeRecipe(pn, id, repeater, ctx) {
7922
8322
  if (pn.recipe === PatternRecipe.Accordion)
7923
8323
  return composeAccordion(pn, id, ctx);
8324
+ if (pn.recipe === PatternRecipe.Tabs)
8325
+ return composeTabs(pn, id, ctx);
8326
+ if (pn.recipe === PatternRecipe.Carousel)
8327
+ return composeCarousel(pn, id, ctx);
8328
+ if (pn.recipe === PatternRecipe.Lightbox)
8329
+ return composeLightbox(pn, id, repeater, ctx);
7924
8330
  if (pn.recipe !== PatternRecipe.Card)
7925
8331
  throw new ComposeError(`${id}: no recipe "${pn.recipe}" in composite-core`);
7926
8332
  const layout = (pn.layout ?? CardLayout.Top);
@@ -8006,12 +8412,128 @@ function composeAccordion(pn, id, ctx) {
8006
8412
  addBinding(accordionAnswerOf(template), { prop: ACCORDION_BIND_PROP, source: binding, path: picks.answer });
8007
8413
  return root;
8008
8414
  }
8415
+ /** The content type a section binding lists, through its data source. */
8416
+ function boundContentType(binding, ctx) {
8417
+ const source = ctx.plan.dataSources.find(d => d.alias === binding);
8418
+ return source && 'contentType' in source ? ctx.plan.contentTypes.find(t => t.name === source.contentType) : undefined;
8419
+ }
8420
+ const TABS_ROOT_KEY = 'tabs';
8421
+ const TABS_HOLDER_KEYS = new Set(['list', 'panels']);
8422
+ /**
8423
+ * Tabs (CC4a) — the composite-core recipe stamped with this composer's factory. Manual (no section binding): three
8424
+ * tabs whose words are placeholders the studio rewrites (`ph: 'copy'`) and whose pictures it supplies — never invented.
8425
+ * From content (the section names a list `binding`, R3's AI path): the tab list and the panels holder are BOTH
8426
+ * Repeaters over that list, each holding its one template (one row → its tab AND its panel, paired by the row's id at
8427
+ * render), the tab's words, the panel's words and its picture bound to the type's first short-text, long-text and image
8428
+ * fields (`pickTabsAutoBinds`, the builder switch's rule), limit 6. The tab list is named by the pattern's `name`.
8429
+ */
8430
+ function composeTabs(pn, id, ctx) {
8431
+ const binding = pn.src === PatternDirective.OptionalBinding || pn.src === PatternDirective.Binding ? ctx.section?.binding ?? null : null;
8432
+ if (pn.src === PatternDirective.Binding && !binding)
8433
+ throw new ComposeError(`${id}: "$binding" with no section binding`);
8434
+ const factory = (spec) => {
8435
+ const type = binding && TABS_HOLDER_KEYS.has(spec.key) ? REPEATER : spec.type;
8436
+ return create(spec.key === TABS_ROOT_KEY ? id : `${id}-${spec.key}`, type, { ...(spec.props ?? {}) }, spec.elementStyle ?? null);
8437
+ };
8438
+ const root = buildTabsRecipe(factory, { tabs: binding ? TABS_WORDS.slice(0, 1) : undefined, label: pn.name });
8439
+ const tree = root;
8440
+ if (!binding) {
8441
+ if (pn.ph === PatternPlaceholder.Copy) {
8442
+ for (const tab of tabsTriggers(tree)) {
8443
+ const words = tabLabelNodeOf(tab);
8444
+ if (words)
8445
+ ctx.placeholders.push({ nodeId: words.id, owner: ctx.owner, kind: PlaceholderKind.Copy, text: String(words.props?.[TABS_LABEL_BIND_PROP] ?? '') });
8446
+ }
8447
+ for (const panel of tabsPanels(tree)) {
8448
+ const text = tabPanelTextOf(panel);
8449
+ if (text)
8450
+ ctx.placeholders.push({ nodeId: text.id, owner: ctx.owner, kind: PlaceholderKind.Copy, text: String(text.props?.[TABS_TEXT_BIND_PROP] ?? '') });
8451
+ const image = tabPanelImageOf(panel);
8452
+ if (image)
8453
+ ctx.placeholders.push({ nodeId: image.id, owner: ctx.owner, kind: PlaceholderKind.Image, text: '' });
8454
+ }
8455
+ }
8456
+ return root;
8457
+ }
8458
+ const type = boundContentType(binding, ctx);
8459
+ const picks = pickTabsAutoBinds((type?.fields ?? []).map(f => ({ name: f.name, type: f.type, controller: f.controller })));
8460
+ if (!picks.label || !picks.text) {
8461
+ throw new ComposeError(`${id}: "${binding}" has no short-text field for the tab and long-text field for its panel`);
8462
+ }
8463
+ for (const holder of [tabsListOf(tree), tabsPanelsOf(tree)]) {
8464
+ addBinding(holder, { prop: 'source', source: binding, path: '' });
8465
+ setProp(holder, 'limit', TABS_CONTENT_LIMIT);
8466
+ }
8467
+ const [tab] = tabsTriggers(tree);
8468
+ const [panel] = tabsPanels(tree);
8469
+ delete tab.itemKey;
8470
+ delete panel.itemKey;
8471
+ addBinding(tabLabelNodeOf(tab), { prop: TABS_LABEL_BIND_PROP, source: binding, path: picks.label });
8472
+ addBinding(tabPanelTextOf(panel), { prop: TABS_TEXT_BIND_PROP, source: binding, path: picks.text });
8473
+ const image = tabPanelImageOf(panel);
8474
+ if (image && picks.image) {
8475
+ addBinding(image, { prop: TABS_IMAGE_BIND_PROP, source: binding, path: picks.image });
8476
+ addBinding(image, { prop: 'alt', source: binding, path: picks.label });
8477
+ }
8478
+ return root;
8479
+ }
8480
+ /** A recipe's node factory for this composer: ids derive from the recipe's keys (`<id>-<key>`, the root `<id>`). */
8481
+ function recipeFactory(id, rootKey) {
8482
+ return spec => create(spec.key === rootKey ? id : `${id}-${spec.key}`, spec.type, { ...(spec.props ?? {}) }, spec.elementStyle ?? null);
8483
+ }
8484
+ /**
8485
+ * CC3: the testimonial carousel — composite-core's recipe (the Slider with its control parts), stamped with this
8486
+ * composer's factory. Every quote and every "client · project" line is a placeholder the studio rewrites (`ph: copy`):
8487
+ * testimonials are facts, never invented.
8488
+ */
8489
+ function composeCarousel(pn, id, ctx) {
8490
+ const slider = buildCarouselRecipe(recipeFactory(id, 'carousel'));
8491
+ if (pn.ph === PatternPlaceholder.Copy) {
8492
+ for (const slide of (slider.children ?? []).filter(c => c.type === SLIDE_TYPE)) {
8493
+ for (const text of slide.children ?? [])
8494
+ ctx.placeholders.push({ nodeId: text.id, owner: ctx.owner, kind: PlaceholderKind.Copy, text: String(text.props?.['content'] ?? '') });
8495
+ }
8496
+ }
8497
+ return slider;
8498
+ }
8499
+ /**
8500
+ * CC3: the Lightbox — a Dialog holding a Slider bound to the node's `src` (the gallery's own source): one template
8501
+ * slide whose image reads the row (a bare URL row: decorative alt), its arrows hidden on a phone.
8502
+ */
8503
+ function composeLightbox(pn, id, repeater, ctx) {
8504
+ if (!pn.src)
8505
+ throw new ComposeError(`${id}: a lightbox needs the gallery's source (src)`);
8506
+ const scoped = resolveScoped(pn.src, repeater, ctx, id);
8507
+ const sourcePath = scoped.path === ITEM_VALUE_PATH ? '' : scoped.path;
8508
+ return buildLightboxRecipe(recipeFactory(id, 'lightbox'), { source: scoped.source, sourcePath, imageField: 'item' });
8509
+ }
8510
+ /** CC3: every `opens: lightbox` node of a stamped pattern opens the pattern's lightbox at its own row. */
8511
+ function wireOpeners(root, openers) {
8512
+ if (!openers.length)
8513
+ return;
8514
+ let lightbox = null;
8515
+ const find = (n) => { if (!lightbox && n.type === DIALOG && n.label === LIGHTBOX_LABEL)
8516
+ lightbox = n; (n.children ?? []).forEach(find); };
8517
+ find(root);
8518
+ if (!lightbox)
8519
+ throw new ComposeError(`${root.id}: "opens: lightbox" with no lightbox in the pattern`);
8520
+ const target = lightbox.id;
8521
+ const visit = (n) => {
8522
+ if (openers.includes(n.id)) {
8523
+ n.interactions = [{ id: `${n.id}-opens`, trigger: 'click', actions: [{ type: 'openDialog', target, params: { slideTo: SLIDE_TO_ROW }, duration: 0, delay: 0, easing: 'ease' }] }];
8524
+ }
8525
+ (n.children ?? []).forEach(visit);
8526
+ };
8527
+ visit(root);
8528
+ }
8009
8529
  function composePattern(pattern, id, ctx) {
8010
8530
  if (pattern.node.each)
8011
8531
  throw new ComposeError(`${id}: a pattern's root cannot be stamped per page`);
8012
- const node = composeNode(pattern.node, id, null, ctx);
8532
+ const own = { ...ctx, openers: [] };
8533
+ const node = composeNode(pattern.node, id, null, own);
8013
8534
  if (!node)
8014
8535
  throw new ComposeError(`${id}: a pattern's root cannot be optional or a link`);
8536
+ wireOpeners(node, own.openers ?? []);
8015
8537
  node.label = pattern.label;
8016
8538
  return node;
8017
8539
  }
@@ -8129,5 +8651,5 @@ function composeSite(plan, context) {
8129
8651
  * Generated bundle index. Do not edit.
8130
8652
  */
8131
8653
 
8132
- export { BAR_ITEMS_MAX, BAR_ITEMS_MIN, BASELINE_ROLES, BaselineRole, BindScope, CardBindTarget, ComposeError, ComposeIssueCode, DANGLING_LINK_CODES, DEFAULT_PHONE_MENU_BREAKPOINT, FACT_KEYS, GlobalSlot, NAV_PART_LABELS, NAV_SHAPE_COPY, NEUTRAL_STYLE_PROPS, NODE_BASELINES, NavRole, NavShape, NavShapeError, PatternDirective, PatternGlobalPosition, PatternPlaceholder, PatternRecipe, PhoneMenuBreakpoint, PlaceholderKind, PlanDataSourceMode, PlanDataSourceType, RelativeOp, SAMPLE_IMAGE_REF, SITE_FACT_LABELS, SITE_PATTERNS, SITE_PLAN_VERSION, SITE_TEMPLATES, SPEC_SITE_FACTS, SPEC_SITE_PATTERNS, SPEC_SITE_PLAN_SHAPE, SPEC_SITE_TEMPLATES, STUDIO_TEMPLATE, SitePlanError, SitePlanIssueCode, SitePlanSourceKind, SiteTemplateError, SiteTemplateId, addBarItem, altSourceScopes, assertStyleSlots, assertValidSitePlan, barIconForRoute, composeSite, createSerializedNode, findNavRoot, navPages, navShapeBreakpoint, navShapeOf, nextSourceOf, nodeBindPaths, parseSampleImageRef, patternById, patternNeedsBinding, patternNodes, patternOptionalParts, patternScopes, patternSlots, patternTakesBinding, planFromTemplate, planNavShape, releasedLayoutDefaults, requiredShapeKeys, routeParams, sampleImageRef, setStyleProps, shapeKeys, validateSitePlan, walkSerialized };
8654
+ export { BAR_ITEMS_MAX, BAR_ITEMS_MIN, BASELINE_ROLES, BaselineRole, BindScope, CardBindTarget, ComposeError, ComposeIssueCode, DANGLING_LINK_CODES, DEFAULT_PHONE_MENU_BREAKPOINT, FACT_KEYS, GlobalSlot, NAV_PART_LABELS, NAV_SHAPE_COPY, NEUTRAL_STYLE_PROPS, NODE_BASELINES, NavRole, NavShape, NavShapeError, PatternDirective, PatternGlobalPosition, PatternOpens, PatternPlaceholder, PatternRecipe, PhoneMenuBreakpoint, PlaceholderKind, PlanDataSourceMode, PlanDataSourceType, RelativeOp, SAMPLE_IMAGE_REF, SITE_FACT_LABELS, SITE_PATTERNS, SITE_PLAN_VERSION, SITE_TEMPLATES, SPEC_SITE_FACTS, SPEC_SITE_PATTERNS, SPEC_SITE_PLAN_SHAPE, SPEC_SITE_TEMPLATES, STUDIO_TEMPLATE, SitePlanError, SitePlanIssueCode, SitePlanSourceKind, SiteTemplateError, SiteTemplateId, addBarItem, altSourceScopes, assertStyleSlots, assertValidSitePlan, barIconForRoute, composeSite, createSerializedNode, findNavRoot, navPages, navShapeBreakpoint, navShapeOf, nextSourceOf, nodeBindPaths, parseSampleImageRef, patternById, patternNeedsBinding, patternNodes, patternOptionalParts, patternScopes, patternSlots, patternTakesBinding, planFromTemplate, planNavShape, releasedLayoutDefaults, requiredShapeKeys, routeParams, sampleImageRef, setStyleProps, shapeKeys, validateSitePlan, walkSerialized };
8133
8655
  //# sourceMappingURL=vectoriox-iox-ui-generation-core.mjs.map