@vectoriox/iox-ui 5.63.0 → 5.65.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/actions-core/index.d.ts +62 -2
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs +80 -1
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs +174 -34
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
- package/generation-core/index.d.ts +29 -5
- package/package.json +1 -1
- package/src/actions-core/dialog-motion.ts +104 -0
- package/src/actions-core/public-api.ts +1 -0
- package/src/generation-core/compose-site.ts +21 -23
- package/src/generation-core/nav-shape.ts +31 -11
- package/src/generation-core/plan-derived.ts +84 -0
- package/src/generation-core/plan-edits.ts +8 -3
- package/src/generation-core/plan-wire.ts +9 -4
- package/src/generation-core/public-api.ts +2 -0
- package/src/generation-core/recipe-fields.ts +49 -0
- package/src/generation-core/validate-site-plan.ts +15 -2
- package/styles/iox-design.css +67 -8
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NEW_NODE_STYLE_DEFAULTS, defaultElementStyleFor, elementStyleById, ownedTraitsOf, applyElementStyle, flavorPackById, parseHexColor, SPEC_AXES, EntranceParam, staggerDelay, LinkType, COLOR_SCHEMES, setColorScheme, resolveDesignChoices, ANIMATE_IDS, auditDesignContrast } from '@vectoriox/iox-ui/design-core';
|
|
2
2
|
import { formPresetById, FORM_PRESETS, ControlType, FORM_NODE_TYPE, collectFormSpec, FIELD_NODE_TYPE } from '@vectoriox/iox-ui/forms-core';
|
|
3
3
|
import { SEO_DESCRIPTION_MAX, SEO_TITLE_MAX } from '@vectoriox/iox-ui/seo-core';
|
|
4
|
-
import { segmentedOptions, ITEM_OPEN_STATE, CardPart, CardRole, CardLayout, CARD_LAYOUTS, CARD_PARTS, BADGE_TONES, buildCardRecipe, findByRole, buildSegmentedRecipe, AccordionRole, buildAccordionRecipe, ACCORDION_WORDS, accordionItems, accordionQuestionOf, accordionAnswerOf, ACCORDION_BIND_PROP,
|
|
4
|
+
import { pickTabsAutoBinds, pickAccordionAutoBinds, segmentedOptions, ITEM_OPEN_STATE, CardPart, CardRole, CardLayout, CARD_LAYOUTS, CARD_PARTS, BADGE_TONES, buildCardRecipe, findByRole, buildSegmentedRecipe, AccordionRole, buildAccordionRecipe, ACCORDION_WORDS, accordionItems, accordionQuestionOf, accordionAnswerOf, ACCORDION_BIND_PROP, ACCORDION_CONTENT_LIMIT, buildTabsRecipe, TABS_WORDS, tabsTriggers, tabLabelNodeOf, TABS_LABEL_BIND_PROP, tabsPanels, tabPanelTextOf, TABS_TEXT_BIND_PROP, tabPanelImageOf, tabsListOf, tabsPanelsOf, TABS_CONTENT_LIMIT, TABS_IMAGE_BIND_PROP, buildCarouselRecipe, SLIDE_TYPE, buildLightboxRecipe, LIGHTBOX_LABEL, SLIDE_TO_ROW } from '@vectoriox/iox-ui/composite-core';
|
|
5
5
|
import { ELEMENT_ACTION_DESCRIPTORS, resolveActionAvailability } from '@vectoriox/iox-ui/actions-core';
|
|
6
6
|
|
|
7
7
|
// GENERATED from iox-ai-guidance flavor-spec.v1.json — do not edit; re-run scripts/gen-flavor-spec.mjs
|
|
@@ -10752,6 +10752,31 @@ function jsonSchemaObjects(node, out = []) {
|
|
|
10752
10752
|
return out;
|
|
10753
10753
|
}
|
|
10754
10754
|
|
|
10755
|
+
const LIST_RECIPES = {
|
|
10756
|
+
[PatternRecipe.Accordion]: { picks: f => ({ ...pickAccordionAutoBinds(f) }), required: ['question', 'answer'], needs: 'short-text field for the question and long-text field for the answer' },
|
|
10757
|
+
[PatternRecipe.Tabs]: { picks: f => ({ ...pickTabsAutoBinds(f) }), required: ['label', 'text'], needs: 'short-text field for the tab and long-text field for its panel' },
|
|
10758
|
+
};
|
|
10759
|
+
/** The fields `recipe` binds from a listed type's `fields`, and what they lack. A recipe that lists nothing lacks nothing. */
|
|
10760
|
+
function recipeListBinds(recipe, fields) {
|
|
10761
|
+
const spec = recipe ? LIST_RECIPES[recipe] : undefined;
|
|
10762
|
+
if (!spec)
|
|
10763
|
+
return { picks: {}, gap: null };
|
|
10764
|
+
const picks = spec.picks(fields);
|
|
10765
|
+
return { picks: picks, gap: spec.required.every(key => picks[key]) ? null : spec.needs };
|
|
10766
|
+
}
|
|
10767
|
+
const listsBinding = (src) => src === PatternDirective.Binding || src === PatternDirective.OptionalBinding;
|
|
10768
|
+
/** What a listed type with `fields` lacks for the pattern's recipes that repeat the section's binding — the first gap, or null. */
|
|
10769
|
+
function patternListGap(pattern, fields) {
|
|
10770
|
+
for (const node of patternNodes(pattern)) {
|
|
10771
|
+
if (!listsBinding(node.src))
|
|
10772
|
+
continue;
|
|
10773
|
+
const { gap } = recipeListBinds(node.recipe, fields);
|
|
10774
|
+
if (gap)
|
|
10775
|
+
return gap;
|
|
10776
|
+
}
|
|
10777
|
+
return null;
|
|
10778
|
+
}
|
|
10779
|
+
|
|
10755
10780
|
/**
|
|
10756
10781
|
* The Site Plan validator — the gate in front of the composer. The wizard's plan and (later) the AI's
|
|
10757
10782
|
* plan go through the SAME checks: an unknown pattern, a slot the pattern does not declare, a source
|
|
@@ -10972,10 +10997,11 @@ function validateContentTypes(c, list) {
|
|
|
10972
10997
|
if (name && types.has(name))
|
|
10973
10998
|
c.add(SitePlanIssueCode.Duplicate, `${path}.name`, `content type "${name}" is used twice`);
|
|
10974
10999
|
else if (name)
|
|
10975
|
-
types.set(name, { fields: fieldSet, roles });
|
|
11000
|
+
types.set(name, { fields: fieldSet, described: fields.filter(isPlainObject).map(describeField), roles });
|
|
10976
11001
|
});
|
|
10977
11002
|
return types;
|
|
10978
11003
|
}
|
|
11004
|
+
const describeField = (f) => ({ name: String(f['name']), type: typeof f['type'] === 'string' ? f['type'] : undefined, controller: typeof f['controller'] === 'string' ? f['controller'] : undefined });
|
|
10979
11005
|
/** A content type's roles (L0-3): known roles only, each played by a field of the type; `title` always; `gallery` a list. */
|
|
10980
11006
|
function validateRoles(c, roles, path, typeName, fields, fieldTypes) {
|
|
10981
11007
|
if (!isPlainObject(roles)) {
|
|
@@ -11202,6 +11228,12 @@ function validatePages(c, list, types, sources, density) {
|
|
|
11202
11228
|
c.add(SitePlanIssueCode.UnknownDataSource, `${sp}.binding`, `no data source "${binding}"`);
|
|
11203
11229
|
else if (src.type !== PlanDataSourceType.Cms || src.mode !== PlanDataSourceMode.Query)
|
|
11204
11230
|
c.add(SitePlanIssueCode.Binding, `${sp}.binding`, `"${binding}" is not a list source`);
|
|
11231
|
+
else {
|
|
11232
|
+
const listed = src.contentType ? types.get(src.contentType) : undefined;
|
|
11233
|
+
const gap = listed ? patternListGap(pattern, listed.described) : null;
|
|
11234
|
+
if (gap)
|
|
11235
|
+
c.add(SitePlanIssueCode.Binding, `${sp}.binding`, `"${binding}" lists ${src.contentType}, which has no ${gap} — bind "${patternId}" to a list that has${patternNeedsBinding(pattern) ? '' : ', or to none'}`);
|
|
11236
|
+
}
|
|
11205
11237
|
}
|
|
11206
11238
|
}
|
|
11207
11239
|
else if (binding)
|
|
@@ -11627,11 +11659,23 @@ function makeKit(globals, rootId, newId) {
|
|
|
11627
11659
|
}
|
|
11628
11660
|
/** Point an opener's dialog actions at `dialogId` (a drawer rebuilt under a new id keeps its openers working). */
|
|
11629
11661
|
function retarget(node, dialogId) {
|
|
11630
|
-
for (const ix of node.interactions) {
|
|
11662
|
+
for (const ix of (node.interactions ?? [])) {
|
|
11631
11663
|
for (const a of ix.actions ?? [])
|
|
11632
|
-
if (a.type ===
|
|
11664
|
+
if (a.type === NavDialogAction.Open || a.type === NavDialogAction.Toggle)
|
|
11633
11665
|
a.target = dialogId;
|
|
11634
11666
|
}
|
|
11667
|
+
return asToggle(node);
|
|
11668
|
+
}
|
|
11669
|
+
/** The menu button and More are TOGGLES (a second click closes what the first opened): a part saved before that
|
|
11670
|
+
* rule opened only — kept through a change of shape, it toggles from then on. */
|
|
11671
|
+
function asToggle(node) {
|
|
11672
|
+
for (const ix of (node.interactions ?? [])) {
|
|
11673
|
+
if (ix.trigger !== CLICK)
|
|
11674
|
+
continue;
|
|
11675
|
+
for (const a of ix.actions ?? [])
|
|
11676
|
+
if (a.type === NavDialogAction.Open)
|
|
11677
|
+
a.type = NavDialogAction.Toggle;
|
|
11678
|
+
}
|
|
11635
11679
|
return node;
|
|
11636
11680
|
}
|
|
11637
11681
|
function part(kit, suffix, type, props, style, role, lay = {}) {
|
|
@@ -11644,6 +11688,13 @@ function part(kit, suffix, type, props, style, role, lay = {}) {
|
|
|
11644
11688
|
Object.assign(node.styleProps, lay);
|
|
11645
11689
|
return node;
|
|
11646
11690
|
}
|
|
11691
|
+
/** The dialog actions a nav part runs on click. */
|
|
11692
|
+
var NavDialogAction;
|
|
11693
|
+
(function (NavDialogAction) {
|
|
11694
|
+
NavDialogAction["Open"] = "openDialog";
|
|
11695
|
+
NavDialogAction["Close"] = "closeDialog";
|
|
11696
|
+
NavDialogAction["Toggle"] = "toggleDialog";
|
|
11697
|
+
})(NavDialogAction || (NavDialogAction = {}));
|
|
11647
11698
|
function clickTo(kit, suffix, type, target) {
|
|
11648
11699
|
return { id: kit.id(`${suffix}-click`), trigger: CLICK, actions: [{ type, target, duration: 0, delay: 0, easing: 'ease' }] };
|
|
11649
11700
|
}
|
|
@@ -11675,10 +11726,14 @@ function iconNode(kit, suffix, iconClass) {
|
|
|
11675
11726
|
function labelText(kit, suffix, words) {
|
|
11676
11727
|
return part(kit, suffix, TEXT$1, { content: words, tag: 'span' }, 'button-label');
|
|
11677
11728
|
}
|
|
11729
|
+
/** The menu button and the drawer's Close are ONE size — and the shared CSS pads the drawer like the header
|
|
11730
|
+
* (`.iox-role-nav-drawer` re-declares dialog-pad as nav-pad) — so the Close lands where the menu button is and
|
|
11731
|
+
* reads as the same button turned into an X. */
|
|
11732
|
+
const MENU_BUTTON_LAY = { width: '48px', height: '48px', alignItems: 'center', justifyContent: 'center' };
|
|
11678
11733
|
function menuButton(kit, drawerId, at) {
|
|
11679
|
-
const btn = part(kit, 'menu', BUTTON, { tag: 'button', ariaLabel: NAV_SHAPE_COPY.menu }, 'button-ghost', NavRole.MenuButton, {
|
|
11734
|
+
const btn = part(kit, 'menu', BUTTON, { tag: 'button', ariaLabel: NAV_SHAPE_COPY.menu }, 'button-ghost', NavRole.MenuButton, { ...MENU_BUTTON_LAY });
|
|
11680
11735
|
btn.children = [iconNode(kit, 'menu-icon', MENU_ICON)];
|
|
11681
|
-
btn.interactions = [clickTo(kit, 'menu',
|
|
11736
|
+
btn.interactions = [clickTo(kit, 'menu', NavDialogAction.Toggle, drawerId)];
|
|
11682
11737
|
phoneOnly(btn, at, 'inline-flex');
|
|
11683
11738
|
return btn;
|
|
11684
11739
|
}
|
|
@@ -11691,9 +11746,9 @@ function drawer(kit, root, pages, at) {
|
|
|
11691
11746
|
}, 'dialog', NavRole.Drawer, { width: '86vw', maxWidth: '360px', display: 'flex', flexDirection: 'column', gap: '20px' });
|
|
11692
11747
|
const brandWords = textOf(find([root], NavRole.Brand) ?? root) || NAV_PART_LABELS[NavRole.Root];
|
|
11693
11748
|
const head = part(kit, 'drawer-head', CONTAINER, {}, 'box', NavRole.DrawerHead, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '12px' });
|
|
11694
|
-
const close = part(kit, 'drawer-close', BUTTON, { tag: 'button', ariaLabel: NAV_SHAPE_COPY.closeMenu }, 'button-ghost', NavRole.DrawerClose, {
|
|
11749
|
+
const close = part(kit, 'drawer-close', BUTTON, { tag: 'button', ariaLabel: NAV_SHAPE_COPY.closeMenu }, 'button-ghost', NavRole.DrawerClose, { ...MENU_BUTTON_LAY });
|
|
11695
11750
|
close.children = [iconNode(kit, 'drawer-close-icon', CLOSE_ICON)];
|
|
11696
|
-
close.interactions = [clickTo(kit, 'drawer-close',
|
|
11751
|
+
close.interactions = [clickTo(kit, 'drawer-close', NavDialogAction.Close, dr.id)];
|
|
11697
11752
|
head.children = [part(kit, 'drawer-brand', HEADING, { content: brandWords, level: 'h3' }, 'h3'), close];
|
|
11698
11753
|
const list = part(kit, 'drawer-links', CONTAINER, {}, 'box', NavRole.DrawerLinks, { display: 'flex', flexDirection: 'column', gap: '4px' });
|
|
11699
11754
|
// Board 1: every page, Home first, at lead size (on each Link: a Link's own baseline font size would
|
|
@@ -11729,7 +11784,7 @@ const BAR_ITEM_LAY = {
|
|
|
11729
11784
|
function moreItem(kit, drawerId) {
|
|
11730
11785
|
const more = part(kit, 'bar-more', BUTTON, { tag: 'button' }, 'bottom-bar-item', NavRole.More, BAR_ITEM_LAY);
|
|
11731
11786
|
more.children = [iconNode(kit, 'bar-more-icon', MORE_ICON), labelText(kit, 'bar-more-label', NAV_SHAPE_COPY.more)];
|
|
11732
|
-
more.interactions = [clickTo(kit, 'bar-more',
|
|
11787
|
+
more.interactions = [clickTo(kit, 'bar-more', NavDialogAction.Toggle, drawerId)];
|
|
11733
11788
|
return more;
|
|
11734
11789
|
}
|
|
11735
11790
|
/** Home first, then the header's pages: the header reaches Home through its brand, a phone menu names it. */
|
|
@@ -11839,7 +11894,7 @@ function planNavShape(globals, shape, options = {}, navId) {
|
|
|
11839
11894
|
dr.props = { ...dr.props, closeAbove: at };
|
|
11840
11895
|
// The menu button: Drawer only (Q4: with a bar, the phone header keeps only the brand).
|
|
11841
11896
|
const existingMenu = removeRole(root, NavRole.MenuButton);
|
|
11842
|
-
const menu = wantMenuButton ? (existingMenu
|
|
11897
|
+
const menu = wantMenuButton ? (existingMenu ? asToggle(existingMenu) : menuButton(kit, dr.id, at)) : null;
|
|
11843
11898
|
for (const p of phoneHiddenParts(root)) {
|
|
11844
11899
|
moveDisplayOverride(p, at);
|
|
11845
11900
|
hideAt(p, at);
|
|
@@ -12698,6 +12753,13 @@ function composeSegmented(pn, id) {
|
|
|
12698
12753
|
applyLayout(root, pn);
|
|
12699
12754
|
return root;
|
|
12700
12755
|
}
|
|
12756
|
+
/** The content type a section binding lists, through its data source. */
|
|
12757
|
+
function boundContentType(binding, ctx) {
|
|
12758
|
+
const source = ctx.plan.dataSources.find(d => d.alias === binding);
|
|
12759
|
+
return source && 'contentType' in source ? ctx.plan.contentTypes.find(t => t.name === source.contentType) : undefined;
|
|
12760
|
+
}
|
|
12761
|
+
/** A type's fields as composite-core's pickers read them. */
|
|
12762
|
+
const fieldsOf = (type) => (type?.fields ?? []).map(f => ({ name: f.name, type: f.type, controller: f.controller }));
|
|
12701
12763
|
/**
|
|
12702
12764
|
* An Accordion (CC2) — the composite-core recipe stamped with this composer's factory. Manual (no section
|
|
12703
12765
|
* binding): three items whose questions and answers are placeholders the studio rewrites (`ph: 'copy'`), never
|
|
@@ -12726,12 +12788,9 @@ function composeAccordion(pn, id, ctx) {
|
|
|
12726
12788
|
}
|
|
12727
12789
|
return root;
|
|
12728
12790
|
}
|
|
12729
|
-
const
|
|
12730
|
-
|
|
12731
|
-
|
|
12732
|
-
if (!picks.question || !picks.answer) {
|
|
12733
|
-
throw new ComposeError(`${id}: "${binding}" has no short-text field for the question and long-text field for the answer`);
|
|
12734
|
-
}
|
|
12791
|
+
const { picks, gap } = recipeListBinds(pn.recipe, fieldsOf(boundContentType(binding, ctx)));
|
|
12792
|
+
if (gap)
|
|
12793
|
+
throw new ComposeError(`${id}: "${binding}" has no ${gap}`);
|
|
12735
12794
|
addBinding(root, { prop: 'source', source: binding, path: '' });
|
|
12736
12795
|
setProp(root, 'limit', ACCORDION_CONTENT_LIMIT);
|
|
12737
12796
|
const [template] = accordionItems(root);
|
|
@@ -12740,11 +12799,6 @@ function composeAccordion(pn, id, ctx) {
|
|
|
12740
12799
|
addBinding(accordionAnswerOf(template), { prop: ACCORDION_BIND_PROP, source: binding, path: picks.answer });
|
|
12741
12800
|
return root;
|
|
12742
12801
|
}
|
|
12743
|
-
/** The content type a section binding lists, through its data source. */
|
|
12744
|
-
function boundContentType(binding, ctx) {
|
|
12745
|
-
const source = ctx.plan.dataSources.find(d => d.alias === binding);
|
|
12746
|
-
return source && 'contentType' in source ? ctx.plan.contentTypes.find(t => t.name === source.contentType) : undefined;
|
|
12747
|
-
}
|
|
12748
12802
|
const TABS_ROOT_KEY = 'tabs';
|
|
12749
12803
|
const TABS_HOLDER_KEYS = new Set(['list', 'panels']);
|
|
12750
12804
|
/**
|
|
@@ -12783,11 +12837,9 @@ function composeTabs(pn, id, ctx) {
|
|
|
12783
12837
|
}
|
|
12784
12838
|
return root;
|
|
12785
12839
|
}
|
|
12786
|
-
const
|
|
12787
|
-
|
|
12788
|
-
|
|
12789
|
-
throw new ComposeError(`${id}: "${binding}" has no short-text field for the tab and long-text field for its panel`);
|
|
12790
|
-
}
|
|
12840
|
+
const { picks, gap } = recipeListBinds(pn.recipe, fieldsOf(boundContentType(binding, ctx)));
|
|
12841
|
+
if (gap)
|
|
12842
|
+
throw new ComposeError(`${id}: "${binding}" has no ${gap}`);
|
|
12791
12843
|
for (const holder of [tabsListOf(tree), tabsPanelsOf(tree)]) {
|
|
12792
12844
|
addBinding(holder, { prop: 'source', source: binding, path: '' });
|
|
12793
12845
|
setProp(holder, 'limit', TABS_CONTENT_LIMIT);
|
|
@@ -13380,6 +13432,85 @@ function buildStepFacts(plan, actions = {}) {
|
|
|
13380
13432
|
};
|
|
13381
13433
|
}
|
|
13382
13434
|
|
|
13435
|
+
/**
|
|
13436
|
+
* What a plan reads but no model is asked to write (vectoriox/iox-cms#49): the data sources that follow from the pages
|
|
13437
|
+
* alone. A per-item page whose sections read the NEXT item (`project.next`) needs a `$siblings.next` source of its route
|
|
13438
|
+
* source — a fact of the pages, while data sources are the plan's STRUCTURE, asked first and fixed once accepted. Asking
|
|
13439
|
+
* the structure for it made the pages' answer unfixable by construction (a re-ask of the pages cannot add a source), so
|
|
13440
|
+
* it is derived, the same way for the plan wire's expansion (`expandPlanWire`) and for a sentence's page edits
|
|
13441
|
+
* (`applyPlanEdits`). Pure and idempotent: a plan that already holds the source gains nothing; a source is derived only
|
|
13442
|
+
* where it is valid by construction (its subject a single cms source of a type of the plan), so it never adds an issue
|
|
13443
|
+
* at a structure path. Its siblings are walked within an unlimited list of the same type — the structure's first, else a
|
|
13444
|
+
* derived one.
|
|
13445
|
+
*/
|
|
13446
|
+
const NEXT_SUFFIX = 'Next';
|
|
13447
|
+
const LIST_SUFFIX = 'All';
|
|
13448
|
+
/** The sources by alias, as the validator reads them (the first of an alias wins). */
|
|
13449
|
+
function sourceViews(list) {
|
|
13450
|
+
const views = new Map();
|
|
13451
|
+
for (const raw of list) {
|
|
13452
|
+
if (isPlainObject(raw) && isNonEmptyString(raw['alias']) && !views.has(raw['alias']))
|
|
13453
|
+
views.set(raw['alias'], raw);
|
|
13454
|
+
}
|
|
13455
|
+
return views;
|
|
13456
|
+
}
|
|
13457
|
+
const readsNext = (page) => Array.isArray(page['sections']) && page['sections'].some(s => {
|
|
13458
|
+
const pattern = isPlainObject(s) && typeof s['pattern'] === 'string' ? patternById(s['pattern']) : undefined;
|
|
13459
|
+
return !!pattern && patternScopes(pattern).has(BindScope.Next);
|
|
13460
|
+
});
|
|
13461
|
+
/** The alias itself, else the first of `<alias>2`, `<alias>3`… no source has. */
|
|
13462
|
+
function freeAlias(base, taken) {
|
|
13463
|
+
let alias = base;
|
|
13464
|
+
for (let n = 2; taken.has(alias); n++)
|
|
13465
|
+
alias = `${base}${n}`;
|
|
13466
|
+
return alias;
|
|
13467
|
+
}
|
|
13468
|
+
/** The unlimited list of `contentType` the plan holds (its first), or null. */
|
|
13469
|
+
function fullListOf(views, contentType) {
|
|
13470
|
+
for (const [alias, s] of views) {
|
|
13471
|
+
if (s.type === PlanDataSourceType.Cms && s.mode === PlanDataSourceMode.Query && s.contentType === contentType && s.limit === undefined)
|
|
13472
|
+
return alias;
|
|
13473
|
+
}
|
|
13474
|
+
return null;
|
|
13475
|
+
}
|
|
13476
|
+
/**
|
|
13477
|
+
* The data sources `plan` lacks and its pages need, in page order (appended after the plan's own). Tolerates any
|
|
13478
|
+
* value: what is not a well-formed plan gains nothing, for the validator to judge as written.
|
|
13479
|
+
*/
|
|
13480
|
+
function derivedDataSources(plan) {
|
|
13481
|
+
if (!isPlainObject(plan) || !Array.isArray(plan['dataSources']) || !Array.isArray(plan['pages']) || !Array.isArray(plan['contentTypes']))
|
|
13482
|
+
return [];
|
|
13483
|
+
const types = new Set(plan['contentTypes'].map(t => (isPlainObject(t) ? t['name'] : undefined)).filter(isNonEmptyString));
|
|
13484
|
+
const views = sourceViews(plan['dataSources']);
|
|
13485
|
+
const derived = [];
|
|
13486
|
+
const add = (source) => { derived.push(source); views.set(source.alias, source); };
|
|
13487
|
+
for (const page of plan['pages']) {
|
|
13488
|
+
if (!isPlainObject(page) || !isNonEmptyString(page['routeSource']) || !readsNext(page))
|
|
13489
|
+
continue;
|
|
13490
|
+
const subject = page['routeSource'];
|
|
13491
|
+
const route = views.get(subject);
|
|
13492
|
+
if (!route || route.type !== PlanDataSourceType.Cms || route.mode !== PlanDataSourceMode.Single)
|
|
13493
|
+
continue;
|
|
13494
|
+
if (!isNonEmptyString(route.contentType) || !types.has(route.contentType) || nextSourceOf(views, subject))
|
|
13495
|
+
continue;
|
|
13496
|
+
let within = fullListOf(views, route.contentType);
|
|
13497
|
+
if (!within) {
|
|
13498
|
+
within = freeAlias(`${subject}${LIST_SUFFIX}`, views);
|
|
13499
|
+
add({ alias: within, type: PlanDataSourceType.Cms, mode: PlanDataSourceMode.Query, contentType: route.contentType });
|
|
13500
|
+
}
|
|
13501
|
+
add({ alias: freeAlias(`${subject}${NEXT_SUFFIX}`, views), type: PlanDataSourceType.Relative, of: subject, op: RelativeOp.SiblingsNext, within });
|
|
13502
|
+
}
|
|
13503
|
+
return derived;
|
|
13504
|
+
}
|
|
13505
|
+
/** `plan` with the data sources its pages need appended (`derivedDataSources`); the same object when it lacks none. */
|
|
13506
|
+
function withDerivedSources(plan) {
|
|
13507
|
+
const derived = derivedDataSources(plan);
|
|
13508
|
+
if (!derived.length)
|
|
13509
|
+
return plan;
|
|
13510
|
+
const own = plan;
|
|
13511
|
+
return { ...own, dataSources: [...own['dataSources'], ...derived] };
|
|
13512
|
+
}
|
|
13513
|
+
|
|
13383
13514
|
/**
|
|
13384
13515
|
* The plan WIRE (AI plan M1-3, vectoriox/iox-cms#45, #47): what a model writes when it plans a site — a compact format
|
|
13385
13516
|
* inside the Claude API's structured-output limits (≤ 24 optional parameters and ≤ 16 unions per request, and a compiled
|
|
@@ -13400,6 +13531,10 @@ function buildStepFacts(plan, actions = {}) {
|
|
|
13400
13531
|
* - **What the model does not decide is not on the wire** (`PLAN_WIRE_DERIVED`): the look (the owner's pick), the run,
|
|
13401
13532
|
* the language (the reading's), the facts (the owner's own, verbatim), the version, the home page (the page at `/`).
|
|
13402
13533
|
* So the format is the same for every studio and every run — one compiled grammar, cached by the provider.
|
|
13534
|
+
* - **What follows from the pages is derived, never asked** (`plan-derived.ts`, vectoriox/iox-cms#49): a per-item page
|
|
13535
|
+
* reading the next item gets its `$siblings.next` source — the structure, asked first and then fixed, cannot know the
|
|
13536
|
+
* pages need it, and the pages cannot add a source. `planToWire` writes every source the plan holds, a derived one
|
|
13537
|
+
* too (it is the plan's: a change's brief shows it, and a plan → wire → plan round trip stays exact).
|
|
13403
13538
|
*
|
|
13404
13539
|
* ONE source: every wire key names the plan keys it carries (`refs`) and is described in the plan table's own words, and
|
|
13405
13540
|
* every plan key is either carried or derived (`planWireGaps()`); a key the plan shape gains breaks the builder until it
|
|
@@ -13444,7 +13579,7 @@ const WIRE_KEYS = {
|
|
|
13444
13579
|
plan: {
|
|
13445
13580
|
business: inPart(PlanPart.Structure, key('plan.business')),
|
|
13446
13581
|
contentTypes: inPart(PlanPart.Structure, key('plan.contentTypes')),
|
|
13447
|
-
dataSources: inPart(PlanPart.Structure, { refs: ['plan.dataSources'], text:
|
|
13582
|
+
dataSources: inPart(PlanPart.Structure, { refs: ['plan.dataSources'], text: "dataSource[] — merged by alias into the org library; a per-item page's $siblings.next source (what a section reading the next item needs) is added for you" }),
|
|
13448
13583
|
pages: inPart(PlanPart.Pages, key('plan.pages')),
|
|
13449
13584
|
globals: inPart(PlanPart.Pages, key('plan.globals')),
|
|
13450
13585
|
},
|
|
@@ -13774,7 +13909,7 @@ function expandWireBusiness(wire, facts) {
|
|
|
13774
13909
|
* A model's plan (its parts, on the wire) + what the generation decides (the look, the run, the language, the owner's
|
|
13775
13910
|
* facts) → the Site Plan. Pure. Translates, never repairs: whatever an answer got wrong is still wrong, for
|
|
13776
13911
|
* `validateSitePlan` — a part that is not an object is the result itself, a part not written yet leaves its keys out
|
|
13777
|
-
* (the validator then names them, at their own paths).
|
|
13912
|
+
* (the validator then names them, at their own paths). What follows from the pages alone is added (`withDerivedSources`).
|
|
13778
13913
|
*/
|
|
13779
13914
|
function expandPlanWire(parts, context) {
|
|
13780
13915
|
const written = PLAN_PARTS.filter(part => parts[part] !== undefined).map(part => parts[part]);
|
|
@@ -13783,7 +13918,7 @@ function expandPlanWire(parts, context) {
|
|
|
13783
13918
|
return odd;
|
|
13784
13919
|
const wire = Object.assign({}, ...written);
|
|
13785
13920
|
const { business, contentTypes, dataSources, pages, ...rest } = wire;
|
|
13786
|
-
return {
|
|
13921
|
+
return withDerivedSources({
|
|
13787
13922
|
...rest,
|
|
13788
13923
|
version: SITE_PLAN_VERSION,
|
|
13789
13924
|
source: { ...context.source },
|
|
@@ -13793,7 +13928,7 @@ function expandPlanWire(parts, context) {
|
|
|
13793
13928
|
contentTypes: each(contentTypes, expandWireContentType),
|
|
13794
13929
|
dataSources: each(dataSources, expandWireDataSource),
|
|
13795
13930
|
pages: each(pages, expandWirePage),
|
|
13796
|
-
};
|
|
13931
|
+
});
|
|
13797
13932
|
}
|
|
13798
13933
|
// ── The plan → the wire ────────────────────────────────────────────────────────────────────────────────────────────
|
|
13799
13934
|
function fieldToWire(field) {
|
|
@@ -14491,8 +14626,9 @@ const APPLY = {
|
|
|
14491
14626
|
};
|
|
14492
14627
|
/**
|
|
14493
14628
|
* Apply a set of edits to a plan — all of them, or none (any issue refuses the set and returns the plan unchanged). The
|
|
14494
|
-
* sources only removed pages and sections read are dropped with them; sources nothing read before are kept
|
|
14495
|
-
*
|
|
14629
|
+
* sources only removed pages and sections read are dropped with them; sources nothing read before are kept; the sources
|
|
14630
|
+
* the pages now need and no model writes are derived (`derivedDataSources` — a page edit adding `project.next`, as the
|
|
14631
|
+
* plan wire's expansion does, vectoriox/iox-cms#49). The result still has to pass `validateSitePlan`.
|
|
14496
14632
|
*/
|
|
14497
14633
|
function applyPlanEdits(plan, edits) {
|
|
14498
14634
|
const structure = validatePlanEdits(edits);
|
|
@@ -14511,7 +14647,11 @@ function applyPlanEdits(plan, edits) {
|
|
|
14511
14647
|
for (const s of draft.dataSources)
|
|
14512
14648
|
if (!dataSources.includes(s))
|
|
14513
14649
|
changed.push({ kind: PlanChangeKind.DataSource, action: PlanChangeAction.Removed, name: s.alias });
|
|
14514
|
-
|
|
14650
|
+
const edited = { ...plan, pages: draft.pages, contentTypes: draft.contentTypes, dataSources };
|
|
14651
|
+
const derived = derivedDataSources(edited);
|
|
14652
|
+
for (const s of derived)
|
|
14653
|
+
changed.push({ kind: PlanChangeKind.DataSource, action: PlanChangeAction.Added, name: s.alias });
|
|
14654
|
+
return { plan: derived.length ? { ...edited, dataSources: [...dataSources, ...derived] } : edited, changed, issues: [] };
|
|
14515
14655
|
}
|
|
14516
14656
|
// ── The format a model writes edits in ─────────────────────────────────────────────────────────────────
|
|
14517
14657
|
/** Each part's answer format name. */
|
|
@@ -15297,5 +15437,5 @@ function addStockCredits(footer, credits) {
|
|
|
15297
15437
|
* Generated bundle index. Do not edit.
|
|
15298
15438
|
*/
|
|
15299
15439
|
|
|
15300
|
-
export { ACTIVE_BUILD_STATUSES, AI_REFUSAL_NEXT, AI_REFUSAL_STATUS, AI_STEPS, AI_STEP_LABELS, AI_STEP_NEEDS, AI_STEP_PART_TOKENS, AI_STEP_TYPICAL_TOKENS, AiCallStatus, AiCapability, AiFeature, AiGenerationStage, AiKeyMode, AiLimit, AiNextKind, AiProviderId, AiRefusalCode, AiStep, BAR_ITEMS_MAX, BAR_ITEMS_MIN, BASELINE_ROLES, BUILD_STEPS, BaselineRole, BindScope, BuildNoticeCode, BuildStep, CONTENT_ROLE_MEANINGS, CREDITS_PER_USD, CardBindTarget, CheckSeverity, ComposeError, ComposeIssueCode, ContentRole, ContentTypeAction, CreatedKind, DANGLING_LINK_CODES, DEFAULT_PHONE_MENU_BREAKPOINT, DEFAULT_SITE_LOCALE, ENTRANCE_EASING, ENTRANCE_ORIGIN_LOOK, ENTRANCE_TRIGGER, EntrancePart, EntranceSide, FACT_KEYS, FINAL_GENERATION_STAGES, FINDING_SEVERITY, GRAMMAR_TOO_LARGE_MESSAGE, GlobalSlot, HEBREW_PREFIXES, JSON_SCHEMA_BANNED_KEYWORDS, JsonSchemaShapeError, KeptReason, NAV_PART_LABELS, NAV_SHAPE_COPY, NEUTRAL_STYLE_PROPS, NODE_BASELINES, NO_TOKENS, NavPagesSource, NavRole, NavShape, NavShapeError, PAGE_ARCHETYPES, PLAN_EDITS_ANSWER_SHAPES, PLAN_EDITS_PART_SCHEMA_NAMES, PLAN_EDIT_OP_PART, PLAN_EDIT_SHAPE, PLAN_EDIT_WIRE_KEYS, PLAN_PARTS, PLAN_PART_KEYS, PLAN_WIRE_DERIVED, PLAN_WIRE_PART_SCHEMA_NAMES, PLAN_WIRE_SHAPE, PRICING_PATTERN, PUBLISH_BLOCKING_PLACEHOLDERS, PageDataKind, PathIssueCollector, PatternDirective, PatternGlobalPosition, PatternOpens, PatternPlaceholder, PatternRecipe, PhoneMenuBreakpoint, PlaceholderKind, PlanChangeAction, PlanChangeKind, PlanDataSourceMode, PlanDataSourceType, PlanEditIssueCode, PlanEditOp, PlanEditsOutcome, PlanPart, PlanWireEntry, PlanWireShapeError, PlanWordIssueCode, PreflightRefusalCode, PublishRefusalCode, REQUIRED_CONTENT_ROLES, RelativeOp, SAMPLE_IMAGE_REF, SITE_CHECKS, SITE_FACT_LABELS, SITE_HEADER_PATTERN, SITE_LIBRARY, SITE_LOCALES, SITE_NEED_IDS, SITE_PATTERNS, SITE_PLAN_VERSION, SITE_TEMPLATES, SPEC_SITE_ARCHETYPES, SPEC_SITE_COPY_LENGTHS, SPEC_SITE_DENSITY_COPY, SPEC_SITE_FACTS, SPEC_SITE_LIBRARY, SPEC_SITE_LOCALES, SPEC_SITE_NEED_IDS, SPEC_SITE_PATTERNS, SPEC_SITE_PLAN_SHAPE, SPEC_SITE_REQUIRED_ROLES, SPEC_SITE_ROLES, SPEC_SITE_TEMPLATES, STOCK_FAMILIES, STOCK_PROVIDER_TERMS, STRICT_SCHEMA_BUDGET, STRICT_SCHEMA_LIMITS, STUDIO_TEMPLATE, SiteBuildKind, SiteBuildStatus, SiteCheckId, SiteFindingCode, SiteLocale, SitePlanError, SitePlanIssueCode, SitePlanSourceKind, SiteTemplateError, SiteTemplateId, StepState, StockProviderId, addBarItem, addStockCredits, aiStepParts, applyPlanEdits, applyStockPhotos, archetypeIssues, assertStyleSlots, assertValidSitePlan, auditSite, barIconForRoute, blocksPublish, buildKeys, buildStepFacts, builtHomePageId, composeNavHeader, composeSection, composeSite, contentTypePreviewFields, contentTypeToWire, copyLengthOf, costUsd, createSerializedNode, creditsOfUsd, dataSourceToWire, describePlanData, describeShapeKey, entranceAction, entranceInteraction, estimateStepCredits, expandPlanWire, expandWireContentType, expandWireDataSource, expandWireEdit, expandWireField, expandWirePage, expandWireSection, failedBuildStep, fieldToWire, findNavRoot, isActiveBuild, isNonEmptyString, isPlainObject, isRtlLocale, jsonAnyOf, jsonBoolean, jsonClosedObject, jsonInteger, jsonListOf, jsonOneOf, jsonSchemaKeywords, jsonSchemaObjects, jsonShapedObject, jsonText, modelCanDo, moveBarItem, navPages, navShapeBreakpoint, navShapeOf, nextSourceOf, nodeBindPaths, pageToWire, parsePatternPath, parseSampleImageRef, pathAt, patternById, patternHasForm, patternLinkSlots, patternManifest, patternNeedsBinding, patternNodes, patternOptionalParts, patternRoleNeeds, patternScopes, patternSlotBudgets, patternSlots, patternTakesBinding, phoneMenuPages, planEditOps, planEditToWire, planEditWireGaps, planEditWireKeys, planEditWireOps, planEditWireShape, planEditsFromWire, planEditsJsonSchema, planEditsToWire, planEntrances, planFromTemplate, planNavShape, planPartOfPath, planPartToWire, planStockPhotos, planToWire, planWireGaps, planWirePartJsonSchema, planWirePartShape, planWordIssues, publishRefusal, publishVerdict, releasedLayoutDefaults, removeBarItem, requiredAcknowledgements, requiredShapeKeys, roleField, routeMatches, routeParams, sampleImageRef, sectionEntrances, sectionPatternIds, sectionToWire, setStyleProps, shapeKeys, showcaseGlobals, showcasePages, showcasePlans, siteNavPages, slotBudget, stockCopyKey, stockCreditLine, stockCreditLines, stockCreditNode, stockCreditNodeId, stockSlots, strictSchemaComplexity, strictSchemaRefusal, typicalStepTokens, validatePlanEdits, validateSitePlan, walkSerialized, wireContentTypeJsonSchema, wireDataSourceJsonSchema, wireFieldJsonSchema, wirePageJsonSchema, wireSectionJsonSchema, wordPattern, writtenTexts };
|
|
15440
|
+
export { ACTIVE_BUILD_STATUSES, AI_REFUSAL_NEXT, AI_REFUSAL_STATUS, AI_STEPS, AI_STEP_LABELS, AI_STEP_NEEDS, AI_STEP_PART_TOKENS, AI_STEP_TYPICAL_TOKENS, AiCallStatus, AiCapability, AiFeature, AiGenerationStage, AiKeyMode, AiLimit, AiNextKind, AiProviderId, AiRefusalCode, AiStep, BAR_ITEMS_MAX, BAR_ITEMS_MIN, BASELINE_ROLES, BUILD_STEPS, BaselineRole, BindScope, BuildNoticeCode, BuildStep, CONTENT_ROLE_MEANINGS, CREDITS_PER_USD, CardBindTarget, CheckSeverity, ComposeError, ComposeIssueCode, ContentRole, ContentTypeAction, CreatedKind, DANGLING_LINK_CODES, DEFAULT_PHONE_MENU_BREAKPOINT, DEFAULT_SITE_LOCALE, ENTRANCE_EASING, ENTRANCE_ORIGIN_LOOK, ENTRANCE_TRIGGER, EntrancePart, EntranceSide, FACT_KEYS, FINAL_GENERATION_STAGES, FINDING_SEVERITY, GRAMMAR_TOO_LARGE_MESSAGE, GlobalSlot, HEBREW_PREFIXES, JSON_SCHEMA_BANNED_KEYWORDS, JsonSchemaShapeError, KeptReason, NAV_PART_LABELS, NAV_SHAPE_COPY, NEUTRAL_STYLE_PROPS, NODE_BASELINES, NO_TOKENS, NavPagesSource, NavRole, NavShape, NavShapeError, PAGE_ARCHETYPES, PLAN_EDITS_ANSWER_SHAPES, PLAN_EDITS_PART_SCHEMA_NAMES, PLAN_EDIT_OP_PART, PLAN_EDIT_SHAPE, PLAN_EDIT_WIRE_KEYS, PLAN_PARTS, PLAN_PART_KEYS, PLAN_WIRE_DERIVED, PLAN_WIRE_PART_SCHEMA_NAMES, PLAN_WIRE_SHAPE, PRICING_PATTERN, PUBLISH_BLOCKING_PLACEHOLDERS, PageDataKind, PathIssueCollector, PatternDirective, PatternGlobalPosition, PatternOpens, PatternPlaceholder, PatternRecipe, PhoneMenuBreakpoint, PlaceholderKind, PlanChangeAction, PlanChangeKind, PlanDataSourceMode, PlanDataSourceType, PlanEditIssueCode, PlanEditOp, PlanEditsOutcome, PlanPart, PlanWireEntry, PlanWireShapeError, PlanWordIssueCode, PreflightRefusalCode, PublishRefusalCode, REQUIRED_CONTENT_ROLES, RelativeOp, SAMPLE_IMAGE_REF, SITE_CHECKS, SITE_FACT_LABELS, SITE_HEADER_PATTERN, SITE_LIBRARY, SITE_LOCALES, SITE_NEED_IDS, SITE_PATTERNS, SITE_PLAN_VERSION, SITE_TEMPLATES, SPEC_SITE_ARCHETYPES, SPEC_SITE_COPY_LENGTHS, SPEC_SITE_DENSITY_COPY, SPEC_SITE_FACTS, SPEC_SITE_LIBRARY, SPEC_SITE_LOCALES, SPEC_SITE_NEED_IDS, SPEC_SITE_PATTERNS, SPEC_SITE_PLAN_SHAPE, SPEC_SITE_REQUIRED_ROLES, SPEC_SITE_ROLES, SPEC_SITE_TEMPLATES, STOCK_FAMILIES, STOCK_PROVIDER_TERMS, STRICT_SCHEMA_BUDGET, STRICT_SCHEMA_LIMITS, STUDIO_TEMPLATE, SiteBuildKind, SiteBuildStatus, SiteCheckId, SiteFindingCode, SiteLocale, SitePlanError, SitePlanIssueCode, SitePlanSourceKind, SiteTemplateError, SiteTemplateId, StepState, StockProviderId, addBarItem, addStockCredits, aiStepParts, applyPlanEdits, applyStockPhotos, archetypeIssues, assertStyleSlots, assertValidSitePlan, auditSite, barIconForRoute, blocksPublish, buildKeys, buildStepFacts, builtHomePageId, composeNavHeader, composeSection, composeSite, contentTypePreviewFields, contentTypeToWire, copyLengthOf, costUsd, createSerializedNode, creditsOfUsd, dataSourceToWire, derivedDataSources, describePlanData, describeShapeKey, entranceAction, entranceInteraction, estimateStepCredits, expandPlanWire, expandWireContentType, expandWireDataSource, expandWireEdit, expandWireField, expandWirePage, expandWireSection, failedBuildStep, fieldToWire, findNavRoot, isActiveBuild, isNonEmptyString, isPlainObject, isRtlLocale, jsonAnyOf, jsonBoolean, jsonClosedObject, jsonInteger, jsonListOf, jsonOneOf, jsonSchemaKeywords, jsonSchemaObjects, jsonShapedObject, jsonText, modelCanDo, moveBarItem, navPages, navShapeBreakpoint, navShapeOf, nextSourceOf, nodeBindPaths, pageToWire, parsePatternPath, parseSampleImageRef, pathAt, patternById, patternHasForm, patternLinkSlots, patternListGap, patternManifest, patternNeedsBinding, patternNodes, patternOptionalParts, patternRoleNeeds, patternScopes, patternSlotBudgets, patternSlots, patternTakesBinding, phoneMenuPages, planEditOps, planEditToWire, planEditWireGaps, planEditWireKeys, planEditWireOps, planEditWireShape, planEditsFromWire, planEditsJsonSchema, planEditsToWire, planEntrances, planFromTemplate, planNavShape, planPartOfPath, planPartToWire, planStockPhotos, planToWire, planWireGaps, planWirePartJsonSchema, planWirePartShape, planWordIssues, publishRefusal, publishVerdict, recipeListBinds, releasedLayoutDefaults, removeBarItem, requiredAcknowledgements, requiredShapeKeys, roleField, routeMatches, routeParams, sampleImageRef, sectionEntrances, sectionPatternIds, sectionToWire, setStyleProps, shapeKeys, showcaseGlobals, showcasePages, showcasePlans, siteNavPages, slotBudget, stockCopyKey, stockCreditLine, stockCreditLines, stockCreditNode, stockCreditNodeId, stockSlots, strictSchemaComplexity, strictSchemaRefusal, typicalStepTokens, validatePlanEdits, validateSitePlan, walkSerialized, wireContentTypeJsonSchema, wireDataSourceJsonSchema, wireFieldJsonSchema, wirePageJsonSchema, wireSectionJsonSchema, withDerivedSources, wordPattern, writtenTexts };
|
|
15301
15441
|
//# sourceMappingURL=vectoriox-iox-ui-generation-core.mjs.map
|