@vectoriox/iox-ui 5.31.2 → 5.32.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 +52 -2
- package/design-core/index.d.ts +650 -6
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs +69 -1
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-design-core.mjs +791 -20
- package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs +564 -22
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui.mjs +365 -24
- package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
- package/generation-core/index.d.ts +126 -6
- package/index.d.ts +245 -2
- package/package.json +1 -1
- package/src/actions-core/dialog-opener.ts +89 -0
- package/src/actions-core/public-api.ts +1 -0
- package/src/design-core/contrast-audit.ts +11 -2
- package/src/design-core/element-styles.ts +6 -3
- package/src/design-core/flavor-spec.generated.ts +737 -11
- package/src/design-core/node-classes.ts +39 -4
- package/src/generation-core/compose-site.ts +9 -1
- package/src/generation-core/nav-shape.ts +553 -0
- package/src/generation-core/node-baselines.generated.ts +23 -8
- package/src/generation-core/patterns.ts +9 -3
- package/src/generation-core/public-api.ts +1 -0
- package/src/generation-core/serialized-node.ts +2 -1
- package/src/generation-core/site-spec.generated.ts +8 -2
- package/styles/iox-design.css +94 -0
|
@@ -111,10 +111,12 @@ const SPEC_SITE_PATTERNS = {
|
|
|
111
111
|
"family": "nav",
|
|
112
112
|
"label": "Navigation bar",
|
|
113
113
|
"global": "before",
|
|
114
|
+
"phoneMenu": "drawer",
|
|
114
115
|
"node": {
|
|
115
116
|
"t": "Container",
|
|
116
117
|
"es": "nav",
|
|
117
118
|
"tag": "nav",
|
|
119
|
+
"role": "nav",
|
|
118
120
|
"lay": {
|
|
119
121
|
"display": "flex",
|
|
120
122
|
"flexWrap": "wrap",
|
|
@@ -127,6 +129,7 @@ const SPEC_SITE_PATTERNS = {
|
|
|
127
129
|
"t": "LinkedContainer",
|
|
128
130
|
"es": "box",
|
|
129
131
|
"href": "/",
|
|
132
|
+
"role": "nav.brand",
|
|
130
133
|
"c": [
|
|
131
134
|
{
|
|
132
135
|
"t": "Heading",
|
|
@@ -140,6 +143,7 @@ const SPEC_SITE_PATTERNS = {
|
|
|
140
143
|
{
|
|
141
144
|
"t": "Container",
|
|
142
145
|
"es": "box",
|
|
146
|
+
"role": "nav.links",
|
|
143
147
|
"lay": {
|
|
144
148
|
"display": "flex",
|
|
145
149
|
"flexWrap": "wrap",
|
|
@@ -153,14 +157,16 @@ const SPEC_SITE_PATTERNS = {
|
|
|
153
157
|
"es": "link",
|
|
154
158
|
"each": "$navPages",
|
|
155
159
|
"bind": "page.name",
|
|
156
|
-
"href": "$page.route"
|
|
160
|
+
"href": "$page.route",
|
|
161
|
+
"role": "nav.link"
|
|
157
162
|
},
|
|
158
163
|
{
|
|
159
164
|
"t": "ButtonBlock",
|
|
160
165
|
"es": "button-primary",
|
|
161
166
|
"href": "/contact",
|
|
162
167
|
"slot": "cta",
|
|
163
|
-
"text": "Get in touch"
|
|
168
|
+
"text": "Get in touch",
|
|
169
|
+
"role": "nav.cta"
|
|
164
170
|
}
|
|
165
171
|
]
|
|
166
172
|
}
|
|
@@ -1347,7 +1353,8 @@ const NODE_BASELINES = {
|
|
|
1347
1353
|
"href": "",
|
|
1348
1354
|
"pageRoute": "",
|
|
1349
1355
|
"target": "_self",
|
|
1350
|
-
"type": "button"
|
|
1356
|
+
"type": "button",
|
|
1357
|
+
"ariaLabel": ""
|
|
1351
1358
|
},
|
|
1352
1359
|
"styleProps": {
|
|
1353
1360
|
"position": "static",
|
|
@@ -1439,7 +1446,9 @@ const NODE_BASELINES = {
|
|
|
1439
1446
|
"href",
|
|
1440
1447
|
"pageRoute",
|
|
1441
1448
|
"target",
|
|
1442
|
-
"type"
|
|
1449
|
+
"type",
|
|
1450
|
+
"__accessibilityLabel",
|
|
1451
|
+
"ariaLabel"
|
|
1443
1452
|
],
|
|
1444
1453
|
"styleTraits": [
|
|
1445
1454
|
"position",
|
|
@@ -2484,7 +2493,8 @@ const NODE_BASELINES = {
|
|
|
2484
2493
|
"linkType": "external",
|
|
2485
2494
|
"url": "https://",
|
|
2486
2495
|
"pageRoute": "",
|
|
2487
|
-
"target": "_self"
|
|
2496
|
+
"target": "_self",
|
|
2497
|
+
"ariaLabel": ""
|
|
2488
2498
|
},
|
|
2489
2499
|
"styleProps": {
|
|
2490
2500
|
"position": "static",
|
|
@@ -2575,7 +2585,9 @@ const NODE_BASELINES = {
|
|
|
2575
2585
|
"linkType",
|
|
2576
2586
|
"url",
|
|
2577
2587
|
"pageRoute",
|
|
2578
|
-
"target"
|
|
2588
|
+
"target",
|
|
2589
|
+
"__accessibilityLabel",
|
|
2590
|
+
"ariaLabel"
|
|
2579
2591
|
],
|
|
2580
2592
|
"styleTraits": [
|
|
2581
2593
|
"position",
|
|
@@ -2676,7 +2688,8 @@ const NODE_BASELINES = {
|
|
|
2676
2688
|
"url": "https://",
|
|
2677
2689
|
"pageRoute": "",
|
|
2678
2690
|
"target": "_self",
|
|
2679
|
-
"colorScheme": "default"
|
|
2691
|
+
"colorScheme": "default",
|
|
2692
|
+
"ariaLabel": ""
|
|
2680
2693
|
},
|
|
2681
2694
|
"styleProps": {
|
|
2682
2695
|
"position": "static",
|
|
@@ -2767,7 +2780,9 @@ const NODE_BASELINES = {
|
|
|
2767
2780
|
"url",
|
|
2768
2781
|
"pageRoute",
|
|
2769
2782
|
"target",
|
|
2770
|
-
"colorScheme"
|
|
2783
|
+
"colorScheme",
|
|
2784
|
+
"__accessibilityLabel",
|
|
2785
|
+
"ariaLabel"
|
|
2771
2786
|
],
|
|
2772
2787
|
"styleTraits": [
|
|
2773
2788
|
"position",
|
|
@@ -5816,7 +5831,9 @@ const NODE_BASELINES = {
|
|
|
5816
5831
|
"placement": "center",
|
|
5817
5832
|
"closeOnBackdrop": true,
|
|
5818
5833
|
"showCloseButton": true,
|
|
5819
|
-
"consentRole": false
|
|
5834
|
+
"consentRole": false,
|
|
5835
|
+
"closeAbove": "",
|
|
5836
|
+
"ariaLabel": ""
|
|
5820
5837
|
},
|
|
5821
5838
|
"styleProps": {
|
|
5822
5839
|
"position": "static",
|
|
@@ -5906,7 +5923,11 @@ const NODE_BASELINES = {
|
|
|
5906
5923
|
"placement",
|
|
5907
5924
|
"closeOnBackdrop",
|
|
5908
5925
|
"showCloseButton",
|
|
5909
|
-
"consentRole"
|
|
5926
|
+
"consentRole",
|
|
5927
|
+
"__responsiveLabel",
|
|
5928
|
+
"closeAbove",
|
|
5929
|
+
"__accessibilityLabel",
|
|
5930
|
+
"ariaLabel"
|
|
5910
5931
|
],
|
|
5911
5932
|
"styleTraits": [
|
|
5912
5933
|
"position",
|
|
@@ -6315,7 +6336,7 @@ var SiteTemplateId;
|
|
|
6315
6336
|
const STUDIO_TEMPLATE = SITE_TEMPLATES[SiteTemplateId.Studio];
|
|
6316
6337
|
class SiteTemplateError extends Error {
|
|
6317
6338
|
}
|
|
6318
|
-
const clone$
|
|
6339
|
+
const clone$3 = (value) => JSON.parse(JSON.stringify(value));
|
|
6319
6340
|
/**
|
|
6320
6341
|
* The template as a Site Plan (`source.kind: template`), with the customer's business and look. A
|
|
6321
6342
|
* required page cannot be left out, and an unknown key is refused — nothing is guessed.
|
|
@@ -6332,12 +6353,12 @@ function planFromTemplate(template, input) {
|
|
|
6332
6353
|
return {
|
|
6333
6354
|
version: SITE_PLAN_VERSION,
|
|
6334
6355
|
source: { kind: SitePlanSourceKind.Template, templateId: template.id, templateVersion: template.version },
|
|
6335
|
-
business: clone$
|
|
6336
|
-
design: clone$
|
|
6337
|
-
contentTypes: clone$
|
|
6338
|
-
dataSources: clone$
|
|
6339
|
-
pages: clone$
|
|
6340
|
-
globals: clone$
|
|
6356
|
+
business: clone$3(input.business),
|
|
6357
|
+
design: clone$3(input.design),
|
|
6358
|
+
contentTypes: clone$3(template.contentTypes),
|
|
6359
|
+
dataSources: clone$3(template.dataSources),
|
|
6360
|
+
pages: clone$3(template.pages.filter(p => !omit.has(p.key))),
|
|
6361
|
+
globals: clone$3(template.globals),
|
|
6341
6362
|
};
|
|
6342
6363
|
}
|
|
6343
6364
|
|
|
@@ -6346,7 +6367,7 @@ function planFromTemplate(template, input) {
|
|
|
6346
6367
|
* wrong — never something to work around. */
|
|
6347
6368
|
class ComposeError extends Error {
|
|
6348
6369
|
}
|
|
6349
|
-
const clone$
|
|
6370
|
+
const clone$2 = (value) => JSON.parse(JSON.stringify(value));
|
|
6350
6371
|
/**
|
|
6351
6372
|
* A NEW node of `type`, as the builder creates one (ComponentRegistryService.createConfig, serialized):
|
|
6352
6373
|
* the type's constructor defaults, its text colour released to `inherit` (NEW_NODE_STYLE_DEFAULTS), then
|
|
@@ -6361,8 +6382,8 @@ function createSerializedNode(id, type, props = {}, elementStyle, options = {})
|
|
|
6361
6382
|
const node = {
|
|
6362
6383
|
id,
|
|
6363
6384
|
type,
|
|
6364
|
-
props: { ...clone$
|
|
6365
|
-
styleProps: clone$
|
|
6385
|
+
props: { ...clone$2(baseline.props), ...props },
|
|
6386
|
+
styleProps: clone$2(baseline.styleProps),
|
|
6366
6387
|
bindings: [],
|
|
6367
6388
|
interactions: [],
|
|
6368
6389
|
};
|
|
@@ -6892,6 +6913,518 @@ function assertValidSitePlan(plan) {
|
|
|
6892
6913
|
throw new SitePlanError(result.issues);
|
|
6893
6914
|
}
|
|
6894
6915
|
|
|
6916
|
+
/**
|
|
6917
|
+
* The phone shapes of a navigation bar (complex-components plan CC1, §7a + R1; boards 1–3). ONE pure tree
|
|
6918
|
+
* edit, `planNavShape`, turns a site's header into the shape the author (the Navigation bar's "Phone
|
|
6919
|
+
* menu" section) or the composer (`nav.inline-cta`'s `phoneMenu`) asks for:
|
|
6920
|
+
*
|
|
6921
|
+
* - **Drawer** — a menu button in the header opens a slide-in Dialog at the end edge that lists every
|
|
6922
|
+
* page (board 1). The drawer is a child of the Navigation bar.
|
|
6923
|
+
* - **Bottom bar** — an app-style bar fixed to the bottom of the screen with up to five pages, no drawer
|
|
6924
|
+
* (board 2). The phone header keeps only the brand (Q4).
|
|
6925
|
+
* - **Both** — the bar with four pages and More, which opens the drawer; the drawer is then its own
|
|
6926
|
+
* global, beside the Navigation bar (board 2's layer tree).
|
|
6927
|
+
*
|
|
6928
|
+
* Every part is a real node with a `role` (R2): editable, selectable, styled by its element style. Parts
|
|
6929
|
+
* a shape keeps are passed through untouched, so an author's edits survive a change of shape; the parts
|
|
6930
|
+
* it no longer has are removed (undo brings them back — the caller records ONE history step). Nothing
|
|
6931
|
+
* here reads a clock or a random source: ids come from `newId` (deterministic by default, so the
|
|
6932
|
+
* composer's output is byte-stable).
|
|
6933
|
+
*/
|
|
6934
|
+
var NavShape;
|
|
6935
|
+
(function (NavShape) {
|
|
6936
|
+
NavShape["Drawer"] = "drawer";
|
|
6937
|
+
NavShape["Bar"] = "bar";
|
|
6938
|
+
NavShape["Both"] = "both";
|
|
6939
|
+
})(NavShape || (NavShape = {}));
|
|
6940
|
+
/** The roles of a navigation's parts (the composite plan's `role` field, here for the nav). */
|
|
6941
|
+
var NavRole;
|
|
6942
|
+
(function (NavRole) {
|
|
6943
|
+
NavRole["Root"] = "nav";
|
|
6944
|
+
NavRole["Brand"] = "nav.brand";
|
|
6945
|
+
NavRole["Links"] = "nav.links";
|
|
6946
|
+
NavRole["Link"] = "nav.link";
|
|
6947
|
+
NavRole["Cta"] = "nav.cta";
|
|
6948
|
+
NavRole["MenuButton"] = "nav.menu-button";
|
|
6949
|
+
NavRole["Drawer"] = "nav.drawer";
|
|
6950
|
+
NavRole["DrawerHead"] = "nav.drawer-head";
|
|
6951
|
+
NavRole["DrawerClose"] = "nav.drawer-close";
|
|
6952
|
+
NavRole["DrawerLinks"] = "nav.drawer-links";
|
|
6953
|
+
NavRole["DrawerLink"] = "nav.drawer-link";
|
|
6954
|
+
NavRole["DrawerCta"] = "nav.drawer-cta";
|
|
6955
|
+
NavRole["Bar"] = "nav.bar";
|
|
6956
|
+
NavRole["BarItem"] = "nav.bar-item";
|
|
6957
|
+
NavRole["More"] = "nav.more";
|
|
6958
|
+
})(NavRole || (NavRole = {}));
|
|
6959
|
+
/** The breakpoints a phone menu may switch at (the responsive Breakpoint ids; Q5: Mobile landscape). */
|
|
6960
|
+
var PhoneMenuBreakpoint;
|
|
6961
|
+
(function (PhoneMenuBreakpoint) {
|
|
6962
|
+
PhoneMenuBreakpoint["Tablet"] = "tablet";
|
|
6963
|
+
PhoneMenuBreakpoint["Mobile"] = "mobile";
|
|
6964
|
+
PhoneMenuBreakpoint["Small"] = "small";
|
|
6965
|
+
})(PhoneMenuBreakpoint || (PhoneMenuBreakpoint = {}));
|
|
6966
|
+
const DEFAULT_PHONE_MENU_BREAKPOINT = PhoneMenuBreakpoint.Mobile;
|
|
6967
|
+
/** The bar's item count, More included (Q2: 3–5; iOS and Material both cap at five). */
|
|
6968
|
+
const BAR_ITEMS_MIN = 3;
|
|
6969
|
+
const BAR_ITEMS_MAX = 5;
|
|
6970
|
+
class NavShapeError extends Error {
|
|
6971
|
+
}
|
|
6972
|
+
const CONTAINER = 'Container';
|
|
6973
|
+
const DIALOG = 'Dialog';
|
|
6974
|
+
const LINK = 'Link';
|
|
6975
|
+
const LINKED = 'LinkedContainer';
|
|
6976
|
+
const BUTTON = 'ButtonBlock';
|
|
6977
|
+
const ICON = 'Icon';
|
|
6978
|
+
const TEXT$1 = 'Text';
|
|
6979
|
+
const HEADING = 'Heading';
|
|
6980
|
+
const NAV_STYLE = 'nav';
|
|
6981
|
+
const DISPLAY = 'display';
|
|
6982
|
+
const NONE = 'none';
|
|
6983
|
+
const CLICK = 'click';
|
|
6984
|
+
const HOME_ROUTE = '/';
|
|
6985
|
+
const COMPOSED$1 = { releaseLayoutDefaults: true };
|
|
6986
|
+
/** The words the parts carry — site copy, editable like any other (the composer writes English copy). */
|
|
6987
|
+
const NAV_SHAPE_COPY = {
|
|
6988
|
+
menu: 'Menu',
|
|
6989
|
+
closeMenu: 'Close menu',
|
|
6990
|
+
more: 'More',
|
|
6991
|
+
home: 'Home',
|
|
6992
|
+
};
|
|
6993
|
+
/** The layer names of the parts (R2: the tree, the breadcrumb and the toolbar read them). */
|
|
6994
|
+
const NAV_PART_LABELS = {
|
|
6995
|
+
[NavRole.Root]: 'Navigation bar',
|
|
6996
|
+
[NavRole.Brand]: 'Brand',
|
|
6997
|
+
[NavRole.Links]: 'Links',
|
|
6998
|
+
[NavRole.MenuButton]: 'Menu button',
|
|
6999
|
+
[NavRole.Drawer]: 'Menu drawer',
|
|
7000
|
+
[NavRole.DrawerHead]: 'Drawer header',
|
|
7001
|
+
[NavRole.DrawerClose]: 'Close',
|
|
7002
|
+
[NavRole.DrawerLinks]: 'Drawer links',
|
|
7003
|
+
[NavRole.Bar]: 'Bottom bar',
|
|
7004
|
+
[NavRole.More]: 'More',
|
|
7005
|
+
};
|
|
7006
|
+
/** Phosphor icons for the bar's items by route — there is no page icon field (Q6), so the bar starts here and
|
|
7007
|
+
* the author picks (the icons are ordinary Icon nodes). */
|
|
7008
|
+
const ROUTE_ICONS = [
|
|
7009
|
+
[/^\/?$/, 'ph-thin ph-house'],
|
|
7010
|
+
[/work|project|portfolio/i, 'ph-thin ph-squares-four'],
|
|
7011
|
+
[/about|studio|team/i, 'ph-thin ph-user'],
|
|
7012
|
+
[/journal|blog|news/i, 'ph-thin ph-book-open'],
|
|
7013
|
+
[/contact/i, 'ph-thin ph-chat-circle'],
|
|
7014
|
+
[/shop|store/i, 'ph-thin ph-bag'],
|
|
7015
|
+
];
|
|
7016
|
+
const FALLBACK_ICON = 'ph-thin ph-file';
|
|
7017
|
+
const MENU_ICON = 'ph-thin ph-list';
|
|
7018
|
+
const CLOSE_ICON = 'ph-thin ph-x';
|
|
7019
|
+
const MORE_ICON = 'ph-thin ph-dots-three';
|
|
7020
|
+
function barIconForRoute(route) {
|
|
7021
|
+
return ROUTE_ICONS.find(([re]) => re.test(route))?.[1] ?? FALLBACK_ICON;
|
|
7022
|
+
}
|
|
7023
|
+
// ── reading ─────────────────────────────────────────────────────────────────────────────────
|
|
7024
|
+
const roleOf = (n) => n?.role;
|
|
7025
|
+
function find(nodes, role) {
|
|
7026
|
+
let hit = null;
|
|
7027
|
+
walkSerialized(nodes, n => { if (!hit && n.role === role)
|
|
7028
|
+
hit = n; });
|
|
7029
|
+
return hit;
|
|
7030
|
+
}
|
|
7031
|
+
function findAll(nodes, role) {
|
|
7032
|
+
const out = [];
|
|
7033
|
+
walkSerialized(nodes, n => { if (n.role === role)
|
|
7034
|
+
out.push(n); });
|
|
7035
|
+
return out;
|
|
7036
|
+
}
|
|
7037
|
+
/** The Navigation bar: the global with the root role, else the first global wearing the `nav` style. */
|
|
7038
|
+
function findNavRoot(globals, navId) {
|
|
7039
|
+
if (navId)
|
|
7040
|
+
return globals.find(g => g.id === navId) ?? null;
|
|
7041
|
+
return globals.find(g => g.role === NavRole.Root) ?? globals.find(g => g.elementStyle === NAV_STYLE && g.type === CONTAINER) ?? null;
|
|
7042
|
+
}
|
|
7043
|
+
/** The shape a site's globals are in now, or null when there is no Navigation bar. */
|
|
7044
|
+
function navShapeOf(globals) {
|
|
7045
|
+
if (!findNavRoot(globals))
|
|
7046
|
+
return null;
|
|
7047
|
+
const bar = !!find(globals, NavRole.Bar);
|
|
7048
|
+
const drawer = !!find(globals, NavRole.Drawer);
|
|
7049
|
+
if (bar && drawer)
|
|
7050
|
+
return NavShape.Both;
|
|
7051
|
+
if (bar)
|
|
7052
|
+
return NavShape.Bar;
|
|
7053
|
+
return drawer ? NavShape.Drawer : null;
|
|
7054
|
+
}
|
|
7055
|
+
const hiddenAt = (n) => Object.entries(n.responsive ?? {}).find(([, layer]) => layer?.styleProps?.[DISPLAY] === NONE)?.[0] ?? null;
|
|
7056
|
+
/** The breakpoint the phone shape takes over at, read off the header's links row (default Mobile). */
|
|
7057
|
+
function navShapeBreakpoint(globals) {
|
|
7058
|
+
const links = find(globals, NavRole.Links);
|
|
7059
|
+
const bp = links ? hiddenAt(links) : null;
|
|
7060
|
+
return Object.values(PhoneMenuBreakpoint).includes(bp ?? '') ? bp : DEFAULT_PHONE_MENU_BREAKPOINT;
|
|
7061
|
+
}
|
|
7062
|
+
const prop = (n, name) => n.props?.[name];
|
|
7063
|
+
const isPageLink = (n) => prop(n, 'linkType') === LinkType.Page && typeof prop(n, 'pageRoute') === 'string';
|
|
7064
|
+
function textOf(n) {
|
|
7065
|
+
if (n.type === LINK)
|
|
7066
|
+
return String(prop(n, 'label') ?? '');
|
|
7067
|
+
let words = '';
|
|
7068
|
+
walkSerialized(n.children ?? [], c => { if (!words && (c.type === TEXT$1 || c.type === HEADING))
|
|
7069
|
+
words = String(prop(c, 'content') ?? ''); });
|
|
7070
|
+
return words;
|
|
7071
|
+
}
|
|
7072
|
+
/** The pages the header links to, in order: its Links (W1.1 — one plain Link per page in the navigation). */
|
|
7073
|
+
function navPages(root) {
|
|
7074
|
+
const links = findAll([root], NavRole.Link);
|
|
7075
|
+
const source = links.length ? links : collectHeaderLinks(root);
|
|
7076
|
+
return source.filter(isPageLink).map(n => ({ label: textOf(n), route: String(prop(n, 'pageRoute')) }));
|
|
7077
|
+
}
|
|
7078
|
+
/** A hand-built header: every page Link outside a drawer. */
|
|
7079
|
+
function collectHeaderLinks(root) {
|
|
7080
|
+
const out = [];
|
|
7081
|
+
const visit = (n) => {
|
|
7082
|
+
if (n.type === DIALOG)
|
|
7083
|
+
return;
|
|
7084
|
+
if (n.type === LINK)
|
|
7085
|
+
out.push(n);
|
|
7086
|
+
(n.children ?? []).forEach(visit);
|
|
7087
|
+
};
|
|
7088
|
+
(root.children ?? []).forEach(visit);
|
|
7089
|
+
return out;
|
|
7090
|
+
}
|
|
7091
|
+
/** New ids: the caller's generator, else `<nav id>-<suffix>` made unique against every id already there. */
|
|
7092
|
+
function makeKit(globals, rootId, newId) {
|
|
7093
|
+
if (newId)
|
|
7094
|
+
return { id: newId };
|
|
7095
|
+
const used = new Set();
|
|
7096
|
+
walkSerialized(globals, n => used.add(n.id));
|
|
7097
|
+
return {
|
|
7098
|
+
id: (suffix) => {
|
|
7099
|
+
const base = `${rootId}-${suffix}`;
|
|
7100
|
+
let id = base;
|
|
7101
|
+
for (let n = 2; used.has(id); n++)
|
|
7102
|
+
id = `${base}-${n}`;
|
|
7103
|
+
used.add(id);
|
|
7104
|
+
return id;
|
|
7105
|
+
},
|
|
7106
|
+
};
|
|
7107
|
+
}
|
|
7108
|
+
/** Point an opener's dialog actions at `dialogId` (a drawer rebuilt under a new id keeps its openers working). */
|
|
7109
|
+
function retarget(node, dialogId) {
|
|
7110
|
+
for (const ix of node.interactions) {
|
|
7111
|
+
for (const a of ix.actions ?? [])
|
|
7112
|
+
if (a.type === 'openDialog' || a.type === 'toggleDialog')
|
|
7113
|
+
a.target = dialogId;
|
|
7114
|
+
}
|
|
7115
|
+
return node;
|
|
7116
|
+
}
|
|
7117
|
+
function part(kit, suffix, type, props, style, role, lay = {}) {
|
|
7118
|
+
const node = createSerializedNode(kit.id(suffix), type, props, style, COMPOSED$1);
|
|
7119
|
+
if (role)
|
|
7120
|
+
node.role = role;
|
|
7121
|
+
const label = role ? NAV_PART_LABELS[role] : undefined;
|
|
7122
|
+
if (label)
|
|
7123
|
+
node.label = label;
|
|
7124
|
+
Object.assign(node.styleProps, lay);
|
|
7125
|
+
return node;
|
|
7126
|
+
}
|
|
7127
|
+
function clickTo(kit, suffix, type, target) {
|
|
7128
|
+
return { id: kit.id(`${suffix}-click`), trigger: CLICK, actions: [{ type, target, duration: 0, delay: 0, easing: 'ease' }] };
|
|
7129
|
+
}
|
|
7130
|
+
/** Show at the phone width, hidden above it (base `display: none`, the breakpoint's own `display`). */
|
|
7131
|
+
function phoneOnly(node, at, display) {
|
|
7132
|
+
node.styleProps[DISPLAY] = NONE;
|
|
7133
|
+
node.responsive = { ...(node.responsive ?? {}), [at]: { ...(node.responsive?.[at] ?? {}), styleProps: { ...(node.responsive?.[at]?.styleProps ?? {}), [DISPLAY]: display } } };
|
|
7134
|
+
}
|
|
7135
|
+
/** Hidden at the phone width: the header's links row. */
|
|
7136
|
+
function hideAt(node, at) {
|
|
7137
|
+
node.responsive = { ...(node.responsive ?? {}), [at]: { ...(node.responsive?.[at] ?? {}), styleProps: { ...(node.responsive?.[at]?.styleProps ?? {}), [DISPLAY]: NONE } } };
|
|
7138
|
+
}
|
|
7139
|
+
/** Move every `display` override the shape owns to the new breakpoint. */
|
|
7140
|
+
function moveDisplayOverride(node, to) {
|
|
7141
|
+
for (const [bp, layer] of Object.entries(node.responsive ?? {})) {
|
|
7142
|
+
if (bp === to || !layer?.styleProps || !(DISPLAY in layer.styleProps))
|
|
7143
|
+
continue;
|
|
7144
|
+
const value = layer.styleProps[DISPLAY];
|
|
7145
|
+
const { [DISPLAY]: _drop, ...rest } = layer.styleProps;
|
|
7146
|
+
node.responsive[bp] = { ...layer, styleProps: rest };
|
|
7147
|
+
if (!Object.keys(rest).length && Object.keys(layer).length === 1)
|
|
7148
|
+
delete node.responsive[bp];
|
|
7149
|
+
node.responsive[to] = { ...(node.responsive[to] ?? {}), styleProps: { ...(node.responsive[to]?.styleProps ?? {}), [DISPLAY]: value } };
|
|
7150
|
+
}
|
|
7151
|
+
}
|
|
7152
|
+
function iconNode(kit, suffix, iconClass) {
|
|
7153
|
+
return part(kit, suffix, ICON, { iconClass }, null);
|
|
7154
|
+
}
|
|
7155
|
+
function labelText(kit, suffix, words) {
|
|
7156
|
+
return part(kit, suffix, TEXT$1, { content: words, tag: 'span' }, 'button-label');
|
|
7157
|
+
}
|
|
7158
|
+
function menuButton(kit, drawerId, at) {
|
|
7159
|
+
const btn = part(kit, 'menu', BUTTON, { tag: 'button', ariaLabel: NAV_SHAPE_COPY.menu }, 'button-ghost', NavRole.MenuButton, { width: '48px', height: '48px', alignItems: 'center', justifyContent: 'center' });
|
|
7160
|
+
btn.children = [iconNode(kit, 'menu-icon', MENU_ICON)];
|
|
7161
|
+
btn.interactions = [clickTo(kit, 'menu', 'openDialog', drawerId)];
|
|
7162
|
+
phoneOnly(btn, at, 'inline-flex');
|
|
7163
|
+
return btn;
|
|
7164
|
+
}
|
|
7165
|
+
function pageLink(kit, suffix, page, role, lay = {}) {
|
|
7166
|
+
return part(kit, suffix, LINK, { label: page.label, linkType: LinkType.Page, pageRoute: page.route }, 'link', role, lay);
|
|
7167
|
+
}
|
|
7168
|
+
function drawer(kit, root, pages, at) {
|
|
7169
|
+
const dr = part(kit, 'drawer', DIALOG, {
|
|
7170
|
+
placement: 'end', closeOnBackdrop: true, showCloseButton: false, ariaLabel: NAV_SHAPE_COPY.menu, closeAbove: at,
|
|
7171
|
+
}, 'dialog', NavRole.Drawer, { width: '86vw', maxWidth: '360px', display: 'flex', flexDirection: 'column', gap: '20px' });
|
|
7172
|
+
const brandWords = textOf(find([root], NavRole.Brand) ?? root) || NAV_PART_LABELS[NavRole.Root];
|
|
7173
|
+
const head = part(kit, 'drawer-head', CONTAINER, {}, 'box', NavRole.DrawerHead, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '12px' });
|
|
7174
|
+
const close = part(kit, 'drawer-close', BUTTON, { tag: 'button', ariaLabel: NAV_SHAPE_COPY.closeMenu }, 'button-ghost', NavRole.DrawerClose, { width: '44px', height: '44px', alignItems: 'center', justifyContent: 'center' });
|
|
7175
|
+
close.children = [iconNode(kit, 'drawer-close-icon', CLOSE_ICON)];
|
|
7176
|
+
close.interactions = [clickTo(kit, 'drawer-close', 'closeDialog', dr.id)];
|
|
7177
|
+
head.children = [part(kit, 'drawer-brand', HEADING, { content: brandWords, level: 'h3' }, 'h3'), close];
|
|
7178
|
+
const list = part(kit, 'drawer-links', CONTAINER, {}, 'box', NavRole.DrawerLinks, { display: 'flex', flexDirection: 'column', gap: '4px' });
|
|
7179
|
+
// Board 1: every page, Home first, at lead size (on each Link: a Link's own baseline font size would
|
|
7180
|
+
// beat one inherited from the list). Only the CURRENT page is underlined — the shared CSS keys that on
|
|
7181
|
+
// the list's role (iox-design.css), so no Link holds a literal over its element style.
|
|
7182
|
+
list.children = withHome(root, pages).map((p, i) => pageLink(kit, `drawer-link-${i}`, p, NavRole.DrawerLink, { display: 'flex', alignItems: 'center', minHeight: '48px', fontSize: 'var(--iox-size-lead)' }));
|
|
7183
|
+
dr.children = [head, list];
|
|
7184
|
+
const cta = find([root], NavRole.Cta);
|
|
7185
|
+
if (cta) {
|
|
7186
|
+
const copy = JSON.parse(JSON.stringify(cta));
|
|
7187
|
+
const reid = (n, path) => { n.id = kit.id(path); (n.children ?? []).forEach((c, i) => reid(c, `${path}-${i}`)); };
|
|
7188
|
+
reid(copy, 'drawer-cta');
|
|
7189
|
+
copy.role = NavRole.DrawerCta;
|
|
7190
|
+
copy.styleProps = { ...copy.styleProps, alignSelf: 'stretch' };
|
|
7191
|
+
dr.children.push(copy);
|
|
7192
|
+
}
|
|
7193
|
+
return dr;
|
|
7194
|
+
}
|
|
7195
|
+
function barItem(kit, i, page) {
|
|
7196
|
+
const item = part(kit, `bar-item-${i}`, LINKED, { linkType: LinkType.Page, pageRoute: page.route, target: '_self' }, 'bottom-bar-item', NavRole.BarItem, BAR_ITEM_LAY);
|
|
7197
|
+
item.label = page.label;
|
|
7198
|
+
item.children = [iconNode(kit, `bar-item-${i}-icon`, barIconForRoute(page.route)), labelText(kit, `bar-item-${i}-label`, page.label)];
|
|
7199
|
+
return item;
|
|
7200
|
+
}
|
|
7201
|
+
const BAR_ITEM_LAY = {
|
|
7202
|
+
display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '3px',
|
|
7203
|
+
flexGrow: '1', flexShrink: '1', flexBasis: '0px', minWidth: '0px', minHeight: '52px', padding: '4px 2px', textDecoration: 'none',
|
|
7204
|
+
};
|
|
7205
|
+
function moreItem(kit, drawerId) {
|
|
7206
|
+
const more = part(kit, 'bar-more', BUTTON, { tag: 'button' }, 'bottom-bar-item', NavRole.More, BAR_ITEM_LAY);
|
|
7207
|
+
more.children = [iconNode(kit, 'bar-more-icon', MORE_ICON), labelText(kit, 'bar-more-label', NAV_SHAPE_COPY.more)];
|
|
7208
|
+
more.interactions = [clickTo(kit, 'bar-more', 'openDialog', drawerId)];
|
|
7209
|
+
return more;
|
|
7210
|
+
}
|
|
7211
|
+
/** Home first, then the header's pages: the header reaches Home through its brand, a phone menu names it. */
|
|
7212
|
+
function withHome(root, pages) {
|
|
7213
|
+
const brand = find([root], NavRole.Brand);
|
|
7214
|
+
const home = brand && isPageLink(brand) && prop(brand, 'pageRoute') === HOME_ROUTE && !pages.some(p => p.route === HOME_ROUTE)
|
|
7215
|
+
? [{ label: NAV_SHAPE_COPY.home, route: HOME_ROUTE }] : [];
|
|
7216
|
+
return [...home, ...pages];
|
|
7217
|
+
}
|
|
7218
|
+
/** The bar's pages: Home first (a bottom bar's customary first stop), capped. */
|
|
7219
|
+
function barPages(root, pages, count) {
|
|
7220
|
+
return withHome(root, pages).slice(0, count);
|
|
7221
|
+
}
|
|
7222
|
+
function bar(kit, root, pages, shape, count, drawerId, at) {
|
|
7223
|
+
// The node IS fixed (not only its element style): the builder canvas forces a node's own `position`
|
|
7224
|
+
// with !important, so a stored `static` default would pin the bar into the flow there.
|
|
7225
|
+
const b = part(kit, 'bar', CONTAINER, { htmlTag: 'nav' }, 'bottom-bar', NavRole.Bar, { position: 'fixed', justifyContent: 'space-around', alignItems: 'stretch', gap: '4px', padding: '6px 8px', width: 'auto' });
|
|
7226
|
+
phoneOnly(b, at, 'flex');
|
|
7227
|
+
const itemCount = shape === NavShape.Both ? count - 1 : count;
|
|
7228
|
+
b.children = barPages(root, pages, itemCount).map((p, i) => barItem(kit, i, p));
|
|
7229
|
+
if (shape === NavShape.Both && drawerId)
|
|
7230
|
+
b.children.push(moreItem(kit, drawerId));
|
|
7231
|
+
b.isGlobal = true;
|
|
7232
|
+
b.globalPosition = PatternGlobalPosition.After;
|
|
7233
|
+
return b;
|
|
7234
|
+
}
|
|
7235
|
+
// ── the edit ────────────────────────────────────────────────────────────────────────────────
|
|
7236
|
+
const clone$1 = (v) => JSON.parse(JSON.stringify(v));
|
|
7237
|
+
function removeRole(node, role) {
|
|
7238
|
+
let removed = null;
|
|
7239
|
+
const visit = (n) => {
|
|
7240
|
+
if (!n.children)
|
|
7241
|
+
return;
|
|
7242
|
+
const i = n.children.findIndex(c => c.role === role);
|
|
7243
|
+
if (i >= 0) {
|
|
7244
|
+
removed = n.children[i];
|
|
7245
|
+
n.children.splice(i, 1);
|
|
7246
|
+
return;
|
|
7247
|
+
}
|
|
7248
|
+
n.children.forEach(visit);
|
|
7249
|
+
};
|
|
7250
|
+
visit(node);
|
|
7251
|
+
return removed;
|
|
7252
|
+
}
|
|
7253
|
+
/** The root's own parts that hide at the phone width: everything but the brand, the menu button and the drawer. */
|
|
7254
|
+
function phoneHiddenParts(root) {
|
|
7255
|
+
const keep = new Set([NavRole.Brand, NavRole.MenuButton, NavRole.Drawer]);
|
|
7256
|
+
return (root.children ?? []).filter((c, i) => !keep.has(roleOf(c)) && !(i === 0 && !c.role && c.type === LINKED));
|
|
7257
|
+
}
|
|
7258
|
+
/** Board 2 / board 8: five items either way — five pages (Bottom bar) or four pages + More (Both). */
|
|
7259
|
+
function defaultBarCount(_shape) {
|
|
7260
|
+
return BAR_ITEMS_MAX;
|
|
7261
|
+
}
|
|
7262
|
+
const JUSTIFY = 'justifyContent';
|
|
7263
|
+
const CENTER = 'center';
|
|
7264
|
+
/** Board 2: with a bar, the phone header holds only the brand, centred. The override lives in the phone
|
|
7265
|
+
* breakpoint's layer only; switching back to the drawer (or moving the breakpoint) removes it. */
|
|
7266
|
+
function centerBrandAt(root, at, on) {
|
|
7267
|
+
for (const [bp, layer] of Object.entries(root.responsive ?? {})) {
|
|
7268
|
+
if (layer?.styleProps?.[JUSTIFY] !== CENTER)
|
|
7269
|
+
continue;
|
|
7270
|
+
const { [JUSTIFY]: _drop, ...rest } = layer.styleProps;
|
|
7271
|
+
root.responsive[bp] = { ...layer, styleProps: rest };
|
|
7272
|
+
}
|
|
7273
|
+
if (on)
|
|
7274
|
+
root.responsive = { ...(root.responsive ?? {}), [at]: { ...(root.responsive?.[at] ?? {}), styleProps: { ...(root.responsive?.[at]?.styleProps ?? {}), [JUSTIFY]: CENTER } } };
|
|
7275
|
+
}
|
|
7276
|
+
function clampBarCount(n, shape) {
|
|
7277
|
+
const v = Number.isFinite(n) ? Math.round(n) : defaultBarCount(shape);
|
|
7278
|
+
return Math.min(BAR_ITEMS_MAX, Math.max(BAR_ITEMS_MIN, v));
|
|
7279
|
+
}
|
|
7280
|
+
/**
|
|
7281
|
+
* Put a site's header into `shape`. The Navigation bar is `navId` or the global carrying the root role
|
|
7282
|
+
* (else the first Container wearing `nav`, which this then marks with roles). Returns new globals;
|
|
7283
|
+
* the input is not mutated.
|
|
7284
|
+
*/
|
|
7285
|
+
function planNavShape(globals, shape, options = {}, navId) {
|
|
7286
|
+
if (!Object.values(NavShape).includes(shape))
|
|
7287
|
+
throw new NavShapeError(`unknown phone menu shape "${shape}"`);
|
|
7288
|
+
const out = clone$1(globals);
|
|
7289
|
+
const root = findNavRoot(out, navId);
|
|
7290
|
+
if (!root)
|
|
7291
|
+
throw new NavShapeError('no Navigation bar among the globals');
|
|
7292
|
+
const at = (options.at ?? navShapeBreakpoint(out));
|
|
7293
|
+
if (!Object.values(PhoneMenuBreakpoint).includes(at))
|
|
7294
|
+
throw new NavShapeError(`a phone menu cannot switch at "${at}"`);
|
|
7295
|
+
const kit = makeKit(out, root.id, options.newId);
|
|
7296
|
+
adoptRoles(root);
|
|
7297
|
+
const pages = navPages(root);
|
|
7298
|
+
const wantDrawer = shape !== NavShape.Bar;
|
|
7299
|
+
const wantBar = shape !== NavShape.Drawer;
|
|
7300
|
+
const wantMenuButton = shape === NavShape.Drawer;
|
|
7301
|
+
// The drawer: kept (moved to where the shape puts it) or built; removed for Bar.
|
|
7302
|
+
let dr = removeRole(root, NavRole.Drawer);
|
|
7303
|
+
const topIndex = out.findIndex(g => g.role === NavRole.Drawer);
|
|
7304
|
+
if (!dr && topIndex >= 0)
|
|
7305
|
+
dr = out.splice(topIndex, 1)[0];
|
|
7306
|
+
if (dr) {
|
|
7307
|
+
delete dr.isGlobal;
|
|
7308
|
+
delete dr.globalPosition;
|
|
7309
|
+
}
|
|
7310
|
+
if (wantDrawer && !dr)
|
|
7311
|
+
dr = drawer(kit, root, pages, at);
|
|
7312
|
+
if (!wantDrawer)
|
|
7313
|
+
dr = null;
|
|
7314
|
+
if (dr)
|
|
7315
|
+
dr.props = { ...dr.props, closeAbove: at };
|
|
7316
|
+
// The menu button: Drawer only (Q4: with a bar, the phone header keeps only the brand).
|
|
7317
|
+
const existingMenu = removeRole(root, NavRole.MenuButton);
|
|
7318
|
+
const menu = wantMenuButton ? (existingMenu ?? menuButton(kit, dr.id, at)) : null;
|
|
7319
|
+
for (const p of phoneHiddenParts(root)) {
|
|
7320
|
+
moveDisplayOverride(p, at);
|
|
7321
|
+
hideAt(p, at);
|
|
7322
|
+
}
|
|
7323
|
+
centerBrandAt(root, at, wantBar);
|
|
7324
|
+
const kids = root.children ?? (root.children = []);
|
|
7325
|
+
if (menu) {
|
|
7326
|
+
moveDisplayOverride(menu, at);
|
|
7327
|
+
kids.push(menu);
|
|
7328
|
+
}
|
|
7329
|
+
if (dr && shape === NavShape.Drawer)
|
|
7330
|
+
kids.push(dr);
|
|
7331
|
+
if (dr && shape === NavShape.Both) {
|
|
7332
|
+
dr.isGlobal = true;
|
|
7333
|
+
dr.globalPosition = PatternGlobalPosition.Before;
|
|
7334
|
+
out.splice(out.indexOf(root) + 1, 0, dr);
|
|
7335
|
+
}
|
|
7336
|
+
// The bar: kept (its items are the author's; More follows the shape) or built; removed for Drawer.
|
|
7337
|
+
const barIndex = out.findIndex(g => g.role === NavRole.Bar);
|
|
7338
|
+
let b = barIndex >= 0 ? out.splice(barIndex, 1)[0] : null;
|
|
7339
|
+
let more = null;
|
|
7340
|
+
if (wantBar) {
|
|
7341
|
+
const count = clampBarCount(options.barItems, shape);
|
|
7342
|
+
if (!b)
|
|
7343
|
+
b = bar(kit, root, pages, shape, count, dr?.id ?? null, at);
|
|
7344
|
+
else {
|
|
7345
|
+
moveDisplayOverride(b, at);
|
|
7346
|
+
more = (b.children ?? []).find(c => c.role === NavRole.More) ?? null;
|
|
7347
|
+
b.children = (b.children ?? []).filter(c => c.role !== NavRole.More);
|
|
7348
|
+
if (options.barItems !== undefined) {
|
|
7349
|
+
const items = b.children;
|
|
7350
|
+
const want = shape === NavShape.Both ? count - 1 : count;
|
|
7351
|
+
const extra = barPages(root, pages, BAR_ITEMS_MAX).filter(p => !items.some(c => prop(c, 'pageRoute') === p.route));
|
|
7352
|
+
while (items.length > want)
|
|
7353
|
+
items.pop();
|
|
7354
|
+
while (items.length < want && extra.length)
|
|
7355
|
+
items.push(barItem(kit, items.length, extra.shift()));
|
|
7356
|
+
}
|
|
7357
|
+
if (shape === NavShape.Both && dr)
|
|
7358
|
+
b.children.push(retarget(more ?? moreItem(kit, dr.id), dr.id));
|
|
7359
|
+
}
|
|
7360
|
+
out.push(b);
|
|
7361
|
+
}
|
|
7362
|
+
return { globals: out, shape, at };
|
|
7363
|
+
}
|
|
7364
|
+
/** Mark a hand-built header's parts with their roles, so every later edit can find them. */
|
|
7365
|
+
function adoptRoles(root) {
|
|
7366
|
+
root.role = NavRole.Root;
|
|
7367
|
+
const kids = root.children ?? [];
|
|
7368
|
+
const brand = kids.find(c => c.role === NavRole.Brand) ?? (kids[0]?.type === LINKED ? kids[0] : null);
|
|
7369
|
+
if (brand)
|
|
7370
|
+
brand.role = NavRole.Brand;
|
|
7371
|
+
for (const c of kids) {
|
|
7372
|
+
if (c === brand || c.role)
|
|
7373
|
+
continue;
|
|
7374
|
+
if (c.type === CONTAINER && (c.children ?? []).some(k => k.type === LINK)) {
|
|
7375
|
+
c.role = NavRole.Links;
|
|
7376
|
+
for (const k of c.children ?? []) {
|
|
7377
|
+
if (k.role)
|
|
7378
|
+
continue;
|
|
7379
|
+
if (k.type === LINK)
|
|
7380
|
+
k.role = NavRole.Link;
|
|
7381
|
+
else if (k.type === BUTTON)
|
|
7382
|
+
k.role = NavRole.Cta;
|
|
7383
|
+
}
|
|
7384
|
+
}
|
|
7385
|
+
}
|
|
7386
|
+
nameParts(root);
|
|
7387
|
+
}
|
|
7388
|
+
/** Board 3's layer names for the header's own parts, where the author has not named them: Brand, Links,
|
|
7389
|
+
* and the call to action by its words ("Get in touch"). */
|
|
7390
|
+
function nameParts(root) {
|
|
7391
|
+
for (const role of [NavRole.Brand, NavRole.Links]) {
|
|
7392
|
+
const part = find([root], role);
|
|
7393
|
+
if (part && !part.label)
|
|
7394
|
+
part.label = NAV_PART_LABELS[role];
|
|
7395
|
+
}
|
|
7396
|
+
const cta = find([root], NavRole.Cta);
|
|
7397
|
+
if (cta && !cta.label) {
|
|
7398
|
+
const words = textOf(cta);
|
|
7399
|
+
if (words)
|
|
7400
|
+
cta.label = words;
|
|
7401
|
+
}
|
|
7402
|
+
}
|
|
7403
|
+
/**
|
|
7404
|
+
* The bar's Items list "Add item" (board 2): one more item, before More — the next navigation page the
|
|
7405
|
+
* bar does not link to yet (Home when every page is there). Refused at five (Q2). Input not mutated.
|
|
7406
|
+
*/
|
|
7407
|
+
function addBarItem(globals, options = {}) {
|
|
7408
|
+
const out = clone$1(globals);
|
|
7409
|
+
const root = findNavRoot(out);
|
|
7410
|
+
const b = find(out, NavRole.Bar);
|
|
7411
|
+
if (!root || !b)
|
|
7412
|
+
return { globals: out, added: false };
|
|
7413
|
+
const kids = b.children ?? (b.children = []);
|
|
7414
|
+
if (kids.length >= BAR_ITEMS_MAX)
|
|
7415
|
+
return { globals: out, added: false };
|
|
7416
|
+
const linked = new Set(kids.filter(c => c.role === NavRole.BarItem).map(c => prop(c, 'pageRoute')));
|
|
7417
|
+
const page = barPages(root, navPages(root), BAR_ITEMS_MAX + 1).find(p => !linked.has(p.route))
|
|
7418
|
+
?? { label: NAV_SHAPE_COPY.home, route: HOME_ROUTE };
|
|
7419
|
+
const item = barItem(makeKit(out, root.id, options.newId), kids.length, page);
|
|
7420
|
+
const more = kids.findIndex(c => c.role === NavRole.More);
|
|
7421
|
+
if (more >= 0)
|
|
7422
|
+
kids.splice(more, 0, item);
|
|
7423
|
+
else
|
|
7424
|
+
kids.push(item);
|
|
7425
|
+
return { globals: out, added: true };
|
|
7426
|
+
}
|
|
7427
|
+
|
|
6895
7428
|
/**
|
|
6896
7429
|
* THE composer — a Site Plan in, ordinary IOX data out (design-system plan §6b, W1). Deterministic and
|
|
6897
7430
|
* pure: the same plan composes to byte-identical output, with no clock, no randomness and no transport.
|
|
@@ -7235,6 +7768,8 @@ function composeNode(pn, id, repeater, ctx) {
|
|
|
7235
7768
|
assertTraits(type, Object.keys(props));
|
|
7236
7769
|
// Props first: a Heading with no `es` wears its LEVEL's default style.
|
|
7237
7770
|
const node = create(id, type, props, pn.es ?? null);
|
|
7771
|
+
if (pn.role)
|
|
7772
|
+
node.role = pn.role;
|
|
7238
7773
|
// Words.
|
|
7239
7774
|
if (words.placeholder) {
|
|
7240
7775
|
const shown = words.value ?? String(NODE_BASELINES[type]?.props[TEXT_PROP[type]] ?? '');
|
|
@@ -7466,13 +8001,20 @@ function composeSite(plan, context) {
|
|
|
7466
8001
|
layout,
|
|
7467
8002
|
};
|
|
7468
8003
|
});
|
|
7469
|
-
|
|
8004
|
+
let globals = Object.values(GlobalSlot).map(slot => {
|
|
7470
8005
|
const pattern = patternById(plan.globals[slot]);
|
|
7471
8006
|
const node = composePattern(pattern, slot, { ...base, owner: slot, page: null, section: null, nextAlias: null });
|
|
7472
8007
|
node.isGlobal = true;
|
|
7473
8008
|
node.globalPosition = pattern.global;
|
|
7474
8009
|
return node;
|
|
7475
8010
|
});
|
|
8011
|
+
// CC1: a header pattern that names its phone shape gets it — the same edit the author's "Phone menu"
|
|
8012
|
+
// section makes, so the composed header and a hand-switched one are one structure.
|
|
8013
|
+
for (const slot of Object.values(GlobalSlot)) {
|
|
8014
|
+
const shape = patternById(plan.globals[slot])?.phoneMenu;
|
|
8015
|
+
if (shape)
|
|
8016
|
+
globals = planNavShape(globals, shape, {}, slot).globals;
|
|
8017
|
+
}
|
|
7476
8018
|
return {
|
|
7477
8019
|
source: clone(plan.source),
|
|
7478
8020
|
business: clone(plan.business),
|
|
@@ -7504,5 +8046,5 @@ function composeSite(plan, context) {
|
|
|
7504
8046
|
* Generated bundle index. Do not edit.
|
|
7505
8047
|
*/
|
|
7506
8048
|
|
|
7507
|
-
export { BASELINE_ROLES, BaselineRole, BindScope, CardBindTarget, ComposeError, ComposeIssueCode, DANGLING_LINK_CODES, FACT_KEYS, GlobalSlot, NEUTRAL_STYLE_PROPS, NODE_BASELINES, PatternDirective, PatternGlobalPosition, PatternPlaceholder, PatternRecipe, 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, altSourceScopes, assertStyleSlots, assertValidSitePlan, composeSite, createSerializedNode, nextSourceOf, nodeBindPaths, parseSampleImageRef, patternById, patternNeedsBinding, patternNodes, patternOptionalParts, patternScopes, patternSlots, planFromTemplate, releasedLayoutDefaults, requiredShapeKeys, routeParams, sampleImageRef, setStyleProps, shapeKeys, validateSitePlan, walkSerialized };
|
|
8049
|
+
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, planFromTemplate, planNavShape, releasedLayoutDefaults, requiredShapeKeys, routeParams, sampleImageRef, setStyleProps, shapeKeys, validateSitePlan, walkSerialized };
|
|
7508
8050
|
//# sourceMappingURL=vectoriox-iox-ui-generation-core.mjs.map
|