@vectoriox/iox-ui 5.38.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 (32) hide show
  1. package/composite-core/index.d.ts +392 -16
  2. package/design-core/index.d.ts +322 -3
  3. package/fesm2022/vectoriox-iox-ui-actions-core.mjs +4 -2
  4. package/fesm2022/vectoriox-iox-ui-actions-core.mjs.map +1 -1
  5. package/fesm2022/vectoriox-iox-ui-composite-core.mjs +1129 -146
  6. package/fesm2022/vectoriox-iox-ui-composite-core.mjs.map +1 -1
  7. package/fesm2022/vectoriox-iox-ui-design-core.mjs +383 -3
  8. package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
  9. package/fesm2022/vectoriox-iox-ui-generation-core.mjs +125 -3
  10. package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
  11. package/fesm2022/vectoriox-iox-ui.mjs +16 -9
  12. package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
  13. package/generation-core/index.d.ts +43 -0
  14. package/index.d.ts +11 -6
  15. package/package.json +1 -1
  16. package/src/actions-core/element-actions.ts +4 -2
  17. package/src/composite-core/accordion-runtime.ts +3 -15
  18. package/src/composite-core/accordion.ts +4 -2
  19. package/src/composite-core/composite-kinds.ts +121 -7
  20. package/src/composite-core/composite-runtime.ts +68 -0
  21. package/src/composite-core/exclusive-runtime.ts +358 -0
  22. package/src/composite-core/public-api.ts +5 -2
  23. package/src/composite-core/structure.ts +22 -2
  24. package/src/composite-core/tabs.ts +628 -0
  25. package/src/design-core/contrast-audit.ts +5 -0
  26. package/src/design-core/element-styles.ts +7 -3
  27. package/src/design-core/flavor-spec.generated.ts +371 -0
  28. package/src/generation-core/compose-site.ts +68 -1
  29. package/src/generation-core/node-baselines.generated.ts +8 -2
  30. package/src/generation-core/patterns.ts +3 -0
  31. package/src/generation-core/site-spec.generated.ts +46 -0
  32. package/styles/iox-design.css +16 -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, buildCarouselRecipe, SLIDE_TYPE, buildLightboxRecipe, LIGHTBOX_LABEL, SLIDE_TO_ROW } 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
@@ -981,6 +981,52 @@ const SPEC_SITE_PATTERNS = {
981
981
  ]
982
982
  }
983
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
+ },
984
1030
  "hero.full-bleed-media": {
985
1031
  "family": "hero",
986
1032
  "label": "Hero — words over a background video",
@@ -2029,7 +2075,10 @@ const NODE_BASELINES = {
2029
2075
  "htmlTag",
2030
2076
  "colorScheme",
2031
2077
  "multiple",
2032
- "defaultOn"
2078
+ "defaultOn",
2079
+ "activateOn",
2080
+ "orientation",
2081
+ "ariaLabel"
2033
2082
  ],
2034
2083
  "styleTraits": [
2035
2084
  "position",
@@ -4262,7 +4311,10 @@ const NODE_BASELINES = {
4262
4311
  "source",
4263
4312
  "limit",
4264
4313
  "multiple",
4265
- "defaultOn"
4314
+ "defaultOn",
4315
+ "activateOn",
4316
+ "orientation",
4317
+ "ariaLabel"
4266
4318
  ],
4267
4319
  "styleTraits": [
4268
4320
  "position",
@@ -6559,6 +6611,9 @@ var PatternRecipe;
6559
6611
  /** CC2: the Accordion — Manual (three placeholder items), or From content when the section names a list
6560
6612
  * `binding` (`src: '$binding?'`): one template item bound to the type's question / answer fields. */
6561
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";
6562
6617
  /** CC3: the accessible carousel — a named Slider whose controls are parts, one quote per slide (placeholders). */
6563
6618
  PatternRecipe["Carousel"] = "carousel";
6564
6619
  /** CC3: the Lightbox — a Dialog holding a Slider over the node's `src` (the gallery's own source). */
@@ -8266,6 +8321,8 @@ const CARD_BINDS = {
8266
8321
  function composeRecipe(pn, id, repeater, ctx) {
8267
8322
  if (pn.recipe === PatternRecipe.Accordion)
8268
8323
  return composeAccordion(pn, id, ctx);
8324
+ if (pn.recipe === PatternRecipe.Tabs)
8325
+ return composeTabs(pn, id, ctx);
8269
8326
  if (pn.recipe === PatternRecipe.Carousel)
8270
8327
  return composeCarousel(pn, id, ctx);
8271
8328
  if (pn.recipe === PatternRecipe.Lightbox)
@@ -8355,6 +8412,71 @@ function composeAccordion(pn, id, ctx) {
8355
8412
  addBinding(accordionAnswerOf(template), { prop: ACCORDION_BIND_PROP, source: binding, path: picks.answer });
8356
8413
  return root;
8357
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
+ }
8358
8480
  /** A recipe's node factory for this composer: ids derive from the recipe's keys (`<id>-<key>`, the root `<id>`). */
8359
8481
  function recipeFactory(id, rootKey) {
8360
8482
  return spec => create(spec.key === rootKey ? id : `${id}-${spec.key}`, spec.type, { ...(spec.props ?? {}) }, spec.elementStyle ?? null);