@vectoriox/iox-ui 5.40.0 → 5.41.1
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/composite-core/index.d.ts +212 -12
- package/design-core/index.d.ts +694 -17
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs +4 -3
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-composite-core.mjs +540 -124
- package/fesm2022/vectoriox-iox-ui-composite-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-design-core.mjs +843 -45
- package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs +846 -17
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui.mjs +3945 -3914
- package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
- package/generation-core/index.d.ts +608 -4
- package/index.d.ts +22 -1
- package/package.json +1 -1
- package/src/actions-core/element-actions.ts +4 -3
- package/src/composite-core/accordion.ts +1 -0
- package/src/composite-core/card.ts +6 -4
- package/src/composite-core/composite-kinds.ts +42 -6
- package/src/composite-core/exclusive-runtime.ts +60 -10
- package/src/composite-core/exclusive.ts +64 -0
- package/src/composite-core/public-api.ts +5 -2
- package/src/composite-core/segmented.ts +351 -0
- package/src/composite-core/structure.ts +6 -1
- package/src/composite-core/tabs.ts +6 -3
- package/src/design-core/contrast-audit.ts +8 -3
- package/src/design-core/flavor-spec.generated.ts +832 -41
- package/src/generation-core/compose-site.ts +170 -12
- package/src/generation-core/node-baselines.generated.ts +49 -0
- package/src/generation-core/patterns.ts +21 -0
- package/src/generation-core/site-spec.generated.ts +639 -0
- package/styles/iox-design.css +19 -0
|
@@ -128,8 +128,9 @@ function removeFromComposite(root, node) {
|
|
|
128
128
|
* The composites' kinds and roles. Two families:
|
|
129
129
|
* - OPEN/CLOSE (CC2 Accordion, CC2.1 Disclosure — composite plan T9 / T21): a Disclosure is a one-item accordion whose
|
|
130
130
|
* ROOT is its item, so both run the same state machine, attribute planners and runtime (`accordion-runtime.ts`);
|
|
131
|
-
* - EXCLUSIVE (CC4 Tabs — composite T17; the Segmented control
|
|
132
|
-
* its panel
|
|
131
|
+
* - EXCLUSIVE (CC4 Tabs — composite T17; CC4b the Segmented control — T32): exactly one item is on; a Tabs' trigger
|
|
132
|
+
* and its panel pair by `itemKey` (`tabs.ts`), a Segmented control's options have no panel (`segmented.ts`); both run
|
|
133
|
+
* `exclusive-runtime.ts`.
|
|
133
134
|
* Every consumer that treats "an item", "a trigger" or "a panel" alike asks these predicates, never a role literal.
|
|
134
135
|
*/
|
|
135
136
|
var CompositeKind;
|
|
@@ -137,6 +138,7 @@ var CompositeKind;
|
|
|
137
138
|
CompositeKind["Accordion"] = "accordion";
|
|
138
139
|
CompositeKind["Disclosure"] = "disclosure";
|
|
139
140
|
CompositeKind["Tabs"] = "tabs";
|
|
141
|
+
CompositeKind["Segmented"] = "segmented";
|
|
140
142
|
})(CompositeKind || (CompositeKind = {}));
|
|
141
143
|
var AccordionRole;
|
|
142
144
|
(function (AccordionRole) {
|
|
@@ -173,12 +175,24 @@ var TabsRole;
|
|
|
173
175
|
TabsRole["Panels"] = "tabs.panels";
|
|
174
176
|
TabsRole["Panel"] = "tabs.panel";
|
|
175
177
|
})(TabsRole || (TabsRole = {}));
|
|
178
|
+
/**
|
|
179
|
+
* SEGMENTED (CC4b — composite T32; complex-components board 10). One of a few, always one on, FIXED options (R3): the
|
|
180
|
+
* root IS the radio group and holds the options directly — no list, no panels. An option may hold the optional saving
|
|
181
|
+
* badge (inside it, so its words join the option's accessible name).
|
|
182
|
+
*/
|
|
183
|
+
var SegmentedRole;
|
|
184
|
+
(function (SegmentedRole) {
|
|
185
|
+
SegmentedRole["Segmented"] = "segmented";
|
|
186
|
+
SegmentedRole["Option"] = "segmented.option";
|
|
187
|
+
SegmentedRole["Badge"] = "segmented.badge";
|
|
188
|
+
})(SegmentedRole || (SegmentedRole = {}));
|
|
176
189
|
const COMPOSITE_KIND_ROLES = {
|
|
177
190
|
[CompositeKind.Accordion]: { root: AccordionRole.Accordion, item: AccordionRole.Item, trigger: AccordionRole.Trigger, panel: AccordionRole.Panel, icon: AccordionRole.Icon },
|
|
178
191
|
[CompositeKind.Disclosure]: { root: DisclosureRole.Disclosure, item: DisclosureRole.Disclosure, trigger: DisclosureRole.Trigger, panel: DisclosureRole.Panel, icon: DisclosureRole.Icon },
|
|
179
192
|
};
|
|
180
193
|
const EXCLUSIVE_KIND_ROLES = {
|
|
181
194
|
[CompositeKind.Tabs]: { root: TabsRole.Tabs, list: TabsRole.List, trigger: TabsRole.Trigger, panels: TabsRole.Panels, panel: TabsRole.Panel },
|
|
195
|
+
[CompositeKind.Segmented]: { root: SegmentedRole.Segmented, list: null, trigger: SegmentedRole.Option, panels: null, panel: null },
|
|
182
196
|
};
|
|
183
197
|
const KINDS = Object.values(CompositeKind);
|
|
184
198
|
const TOGGLE_KINDS = Object.keys(COMPOSITE_KIND_ROLES);
|
|
@@ -282,6 +296,23 @@ function isExclusiveItemPart(node) {
|
|
|
282
296
|
function isTabsPart(node) {
|
|
283
297
|
return compositeKindOfRole(node?.role) === CompositeKind.Tabs;
|
|
284
298
|
}
|
|
299
|
+
/** True for any part of a Segmented control (its root included). */
|
|
300
|
+
function isSegmentedPart(node) {
|
|
301
|
+
return compositeKindOfRole(node?.role) === CompositeKind.Segmented;
|
|
302
|
+
}
|
|
303
|
+
/** The exclusive kind a node's role belongs to (`segmented.option` → Segmented), or null. */
|
|
304
|
+
function exclusiveKindOf(node) {
|
|
305
|
+
const kind = compositeKindOfRole(node?.role);
|
|
306
|
+
return isExclusiveKind(kind) ? kind : null;
|
|
307
|
+
}
|
|
308
|
+
/** True when an exclusive root holds its triggers itself (a Segmented control), not in a list part. */
|
|
309
|
+
function exclusiveRootHoldsTriggers(kind) {
|
|
310
|
+
return EXCLUSIVE_KIND_ROLES[kind].list === null;
|
|
311
|
+
}
|
|
312
|
+
/** True when an exclusive kind's items show a panel (Tabs), so a trigger and its panel are a pair. */
|
|
313
|
+
function exclusiveKindHasPanels(kind) {
|
|
314
|
+
return !!kind && EXCLUSIVE_KIND_ROLES[kind].panel !== null;
|
|
315
|
+
}
|
|
285
316
|
/** A root of either family — anything that runs a composite runtime. */
|
|
286
317
|
function isAnyCompositeRoot(node) {
|
|
287
318
|
return isCompositeRoot(node) || isExclusiveRoot(node);
|
|
@@ -368,7 +399,7 @@ const ACCORDION_WORDS = [
|
|
|
368
399
|
{ question: 'Write the third question', answer: 'Answer it plainly, in two or three short sentences.' },
|
|
369
400
|
];
|
|
370
401
|
const ACCORDION_NEW_ITEM_WORDS = { question: 'Write a new question', answer: 'Answer it plainly, in two or three short sentences.' };
|
|
371
|
-
var Type$
|
|
402
|
+
var Type$5;
|
|
372
403
|
(function (Type) {
|
|
373
404
|
Type["Container"] = "Container";
|
|
374
405
|
Type["LinkedContainer"] = "LinkedContainer";
|
|
@@ -376,8 +407,8 @@ var Type$4;
|
|
|
376
407
|
Type["Text"] = "Text";
|
|
377
408
|
Type["Icon"] = "Icon";
|
|
378
409
|
Type["Repeater"] = "Repeater";
|
|
379
|
-
})(Type$
|
|
380
|
-
var Style$
|
|
410
|
+
})(Type$5 || (Type$5 = {}));
|
|
411
|
+
var Style$5;
|
|
381
412
|
(function (Style) {
|
|
382
413
|
Style["Root"] = "box";
|
|
383
414
|
Style["Item"] = "accordion-item";
|
|
@@ -386,14 +417,14 @@ var Style$4;
|
|
|
386
417
|
Style["Icon"] = "icon";
|
|
387
418
|
Style["Panel"] = "accordion-panel";
|
|
388
419
|
Style["Answer"] = "body";
|
|
389
|
-
})(Style$
|
|
390
|
-
const ZERO_BOX$
|
|
420
|
+
})(Style$5 || (Style$5 = {}));
|
|
421
|
+
const ZERO_BOX$4 = '0px 0px 0px 0px';
|
|
391
422
|
const HEADING_LEVELS = ['h2', 'h3', 'h4', 'h5', 'h6'];
|
|
392
423
|
const DEFAULT_LEVEL = 'h3';
|
|
393
424
|
/** A linked trait the header must never carry: it is a button, not a link. */
|
|
394
425
|
const LINK_TYPE$1 = 'linkType';
|
|
395
426
|
/** What each part holds in the recipe — layout only; its LOOK is its element style. */
|
|
396
|
-
const PART_BASE$
|
|
427
|
+
const PART_BASE$3 = {
|
|
397
428
|
// The items are a column a token apart (`accordion-gap`: 0 for a divider list, 12px for cards).
|
|
398
429
|
[AccordionRole.Accordion]: { display: 'flex', flexDirection: 'column', gap: 'var(--iox-accordion-gap)', width: '100%', minHeight: 'auto', height: 'auto' },
|
|
399
430
|
[AccordionRole.Item]: { display: 'block', width: '100%', minHeight: 'auto', height: 'auto' },
|
|
@@ -404,16 +435,16 @@ const PART_BASE$2 = {
|
|
|
404
435
|
};
|
|
405
436
|
/** The question INHERITS the header's ink (board 4's wears table: the Question reads no colour token), so the
|
|
406
437
|
* header's hover ink reaches the words; and it sits in the row with no margin of its own. */
|
|
407
|
-
const QUESTION_BASE = { color: 'inherit', margin: ZERO_BOX$
|
|
408
|
-
const ANSWER_BASE = { margin: ZERO_BOX$
|
|
409
|
-
function make$
|
|
410
|
-
return makeCompositePart(create, { key, type, props, elementStyle: style }, role, label, role ? PART_BASE$
|
|
438
|
+
const QUESTION_BASE = { color: 'inherit', margin: ZERO_BOX$4, flexGrow: '1', minWidth: '0px' };
|
|
439
|
+
const ANSWER_BASE = { margin: ZERO_BOX$4, padding: ZERO_BOX$4, minHeight: 'auto' };
|
|
440
|
+
function make$4(create, key, type, role, style, props = {}, label) {
|
|
441
|
+
return makeCompositePart(create, { key, type, props, elementStyle: style }, role, label, role ? PART_BASE$3[role] : undefined);
|
|
411
442
|
}
|
|
412
443
|
/** The icon of an item (an accordion's, or a disclosure's — `role`); its Item-open state (the turn) is driven by
|
|
413
444
|
* ITS item (`stateTriggers`). ONE builder for both kinds. */
|
|
414
445
|
function buildCompositeIcon(create, key, icon, itemId, role = AccordionRole.Icon) {
|
|
415
446
|
const glyph = ACCORDION_ICON_GLYPH[icon];
|
|
416
|
-
const node = makeCompositePart(create, { key, type: Type$
|
|
447
|
+
const node = makeCompositePart(create, { key, type: Type$5.Icon, props: { iconClass: glyph.iconClass }, elementStyle: Style$5.Icon }, role, 'Icon', PART_BASE$3[AccordionRole.Icon]);
|
|
417
448
|
node.stateStyles = { ...(node.stateStyles ?? {}), [ITEM_OPEN_STATE]: { rotate: glyph.turn } };
|
|
418
449
|
if (itemId)
|
|
419
450
|
node.stateTriggers = { ...(node.stateTriggers ?? {}), [ITEM_OPEN_STATE]: itemId };
|
|
@@ -421,14 +452,14 @@ function buildCompositeIcon(create, key, icon, itemId, role = AccordionRole.Icon
|
|
|
421
452
|
}
|
|
422
453
|
/** One item: header (question + icon) and panel (answer). `key` makes every part's recipe key unique. */
|
|
423
454
|
function buildAccordionItem(create, words, itemKey, icon = AccordionIcon.Chevron) {
|
|
424
|
-
const item = make$
|
|
455
|
+
const item = make$4(create, `${itemKey}`, Type$5.Container, AccordionRole.Item, Style$5.Item, {}, 'Item');
|
|
425
456
|
item.itemKey = itemKey;
|
|
426
|
-
const trigger = make$
|
|
427
|
-
const question = make$
|
|
457
|
+
const trigger = make$4(create, `${itemKey}-trigger`, Type$5.LinkedContainer, AccordionRole.Trigger, Style$5.Trigger, {}, 'Header');
|
|
458
|
+
const question = make$4(create, `${itemKey}-question`, Type$5.Heading, null, Style$5.Question, { content: words.question, level: DEFAULT_LEVEL }, 'Heading');
|
|
428
459
|
writeStyles(question, QUESTION_BASE);
|
|
429
460
|
trigger.children = [question, ...(icon === AccordionIcon.None ? [] : [buildCompositeIcon(create, `${itemKey}-icon`, icon, item.id)])];
|
|
430
|
-
const panel = make$
|
|
431
|
-
const answer = make$
|
|
461
|
+
const panel = make$4(create, `${itemKey}-panel`, Type$5.Container, AccordionRole.Panel, Style$5.Panel, {}, 'Panel');
|
|
462
|
+
const answer = make$4(create, `${itemKey}-answer`, Type$5.Text, null, Style$5.Answer, { content: words.answer, tag: 'p' }, 'Text');
|
|
432
463
|
writeStyles(answer, ANSWER_BASE);
|
|
433
464
|
panel.children = [answer];
|
|
434
465
|
item.children = [trigger, panel];
|
|
@@ -436,7 +467,7 @@ function buildAccordionItem(create, words, itemKey, icon = AccordionIcon.Chevron
|
|
|
436
467
|
}
|
|
437
468
|
/** A fresh Accordion: three items, one open at a time, the first open by default (board 4). */
|
|
438
469
|
function buildAccordionRecipe(create, options = {}) {
|
|
439
|
-
const root = make$
|
|
470
|
+
const root = make$4(create, 'accordion', Type$5.Container, AccordionRole.Accordion, Style$5.Root, {}, 'Accordion');
|
|
440
471
|
writeProp(root, CompositeBehaviourTrait.Multiple, options.multiple ?? false);
|
|
441
472
|
writeProp(root, CompositeBehaviourTrait.DefaultOn, options.defaultOn ?? AccordionDefaultOn.First);
|
|
442
473
|
const words = options.items ?? ACCORDION_WORDS;
|
|
@@ -475,7 +506,7 @@ function accordionItemOf(node, path) {
|
|
|
475
506
|
}
|
|
476
507
|
/** True when the root takes its items from content (a Repeater bound to a list, R3). */
|
|
477
508
|
function isAccordionFromContent(root) {
|
|
478
|
-
return !!root && root.type === Type$
|
|
509
|
+
return !!root && root.type === Type$5.Repeater;
|
|
479
510
|
}
|
|
480
511
|
/** The root's items in order — every item (Manual), or the one template item (From content). */
|
|
481
512
|
function accordionItems(root) {
|
|
@@ -522,9 +553,9 @@ function accordionQuestionOf(item) {
|
|
|
522
553
|
let text;
|
|
523
554
|
if (trigger)
|
|
524
555
|
walkComposite(trigger, n => {
|
|
525
|
-
if (!hit && n.type === Type$
|
|
556
|
+
if (!hit && n.type === Type$5.Heading)
|
|
526
557
|
hit = n;
|
|
527
|
-
if (!text && n.type === Type$
|
|
558
|
+
if (!text && n.type === Type$5.Text)
|
|
528
559
|
text = n;
|
|
529
560
|
});
|
|
530
561
|
return hit ?? text;
|
|
@@ -534,15 +565,15 @@ function accordionAnswerOf(item) {
|
|
|
534
565
|
const panel = accordionPanelOf(item);
|
|
535
566
|
let hit;
|
|
536
567
|
if (panel)
|
|
537
|
-
walkComposite(panel, n => { if (!hit && n.type === Type$
|
|
568
|
+
walkComposite(panel, n => { if (!hit && n.type === Type$5.Text)
|
|
538
569
|
hit = n; });
|
|
539
570
|
return hit;
|
|
540
571
|
}
|
|
541
|
-
const stripTags$
|
|
572
|
+
const stripTags$2 = (v) => String(v ?? '').replace(/<[^>]*>/g, '').trim();
|
|
542
573
|
/** An item's name in the Items list and the layer tree: its question's words. */
|
|
543
574
|
function accordionItemLabel(item) {
|
|
544
575
|
const q = accordionQuestionOf(item);
|
|
545
|
-
return q ? stripTags$
|
|
576
|
+
return q ? stripTags$2(readProp(q, 'content')) : '';
|
|
546
577
|
}
|
|
547
578
|
/** Items ▸ rename: the question's words. */
|
|
548
579
|
function renameAccordionItem(item, question) {
|
|
@@ -555,7 +586,7 @@ function accordionHeadingLevel(trigger) {
|
|
|
555
586
|
const visit = (n) => {
|
|
556
587
|
if (level !== undefined)
|
|
557
588
|
return;
|
|
558
|
-
if (n.type === Type$
|
|
589
|
+
if (n.type === Type$5.Heading) {
|
|
559
590
|
level = readProp(n, 'level');
|
|
560
591
|
return;
|
|
561
592
|
}
|
|
@@ -736,6 +767,7 @@ const DOM_ID_PREFIX = {
|
|
|
736
767
|
[CompositeKind.Accordion]: 'iox-acc',
|
|
737
768
|
[CompositeKind.Disclosure]: 'iox-dis',
|
|
738
769
|
[CompositeKind.Tabs]: 'iox-tab',
|
|
770
|
+
[CompositeKind.Segmented]: 'iox-seg',
|
|
739
771
|
};
|
|
740
772
|
/** The ARIA pairing ids of an item: unique per composite (its SCOPE id — `compositeScopeId`) and per item (its key). */
|
|
741
773
|
function accordionDomIds(rootId, itemKey, kind = CompositeKind.Accordion) {
|
|
@@ -1306,21 +1338,21 @@ const DISCLOSURE_WORDS = {
|
|
|
1306
1338
|
};
|
|
1307
1339
|
/** A disclosure's one item key — its root IS its item. */
|
|
1308
1340
|
const DISCLOSURE_ITEM_KEY = 'k1';
|
|
1309
|
-
var Type$
|
|
1341
|
+
var Type$4;
|
|
1310
1342
|
(function (Type) {
|
|
1311
1343
|
Type["Container"] = "Container";
|
|
1312
1344
|
Type["LinkedContainer"] = "LinkedContainer";
|
|
1313
1345
|
Type["Text"] = "Text";
|
|
1314
|
-
})(Type$
|
|
1315
|
-
var Style$
|
|
1346
|
+
})(Type$4 || (Type$4 = {}));
|
|
1347
|
+
var Style$4;
|
|
1316
1348
|
(function (Style) {
|
|
1317
1349
|
Style["Root"] = "box";
|
|
1318
1350
|
Style["Trigger"] = "accordion-trigger";
|
|
1319
1351
|
Style["Word"] = "body";
|
|
1320
1352
|
Style["Panel"] = "accordion-panel";
|
|
1321
1353
|
Style["Body"] = "body";
|
|
1322
|
-
})(Style$
|
|
1323
|
-
const ZERO_BOX$
|
|
1354
|
+
})(Style$4 || (Style$4 = {}));
|
|
1355
|
+
const ZERO_BOX$3 = '0px 0px 0px 0px';
|
|
1324
1356
|
const HIDDEN = 'none';
|
|
1325
1357
|
const INHERIT = 'inherit';
|
|
1326
1358
|
/** A word is a flex item of the toggle — `block` (never `inline`: the compiler drops a value equal to the CSS initial,
|
|
@@ -1329,7 +1361,7 @@ const SHOWN = 'block';
|
|
|
1329
1361
|
/** What each part holds — layout only; its LOOK is its element style. The trigger sizes to its words (a button in
|
|
1330
1362
|
* the flow, not a full-width header row — `fit-content` keeps it from stretching) and the panel spans the root. The
|
|
1331
1363
|
* trigger is `flex`, not `inline-flex`: it is a flex item of the root, which blockifies it on the site anyway. */
|
|
1332
|
-
const PART_BASE$
|
|
1364
|
+
const PART_BASE$2 = {
|
|
1333
1365
|
[DisclosureRole.Disclosure]: { display: 'flex', flexDirection: 'column', width: '100%', minHeight: 'auto', height: 'auto' },
|
|
1334
1366
|
// The toggle reads the text around it (the page's body type, through the root) — a LinkedContainer is born with
|
|
1335
1367
|
// its own literal font, which an accordion's heading masks and a disclosure's words would show.
|
|
@@ -1339,17 +1371,17 @@ const PART_BASE$1 = {
|
|
|
1339
1371
|
},
|
|
1340
1372
|
// A word is just its words: none of a Text's own box (its type's 10px padding, its full width) — the toggle's
|
|
1341
1373
|
// padding is the click target.
|
|
1342
|
-
[DisclosureRole.More]: { display: SHOWN, margin: ZERO_BOX$
|
|
1343
|
-
[DisclosureRole.Less]: { display: HIDDEN, margin: ZERO_BOX$
|
|
1374
|
+
[DisclosureRole.More]: { display: SHOWN, margin: ZERO_BOX$3, padding: ZERO_BOX$3, width: 'auto' },
|
|
1375
|
+
[DisclosureRole.Less]: { display: HIDDEN, margin: ZERO_BOX$3, padding: ZERO_BOX$3, width: 'auto' },
|
|
1344
1376
|
[DisclosureRole.Panel]: { display: 'block', width: '100%', minHeight: 'auto', height: 'auto' },
|
|
1345
1377
|
};
|
|
1346
|
-
const BODY_BASE$1 = { margin: ZERO_BOX$
|
|
1347
|
-
function make$
|
|
1348
|
-
return makeCompositePart(create, { key, type, props, elementStyle: style }, role, label, role ? PART_BASE$
|
|
1378
|
+
const BODY_BASE$1 = { margin: ZERO_BOX$3, padding: ZERO_BOX$3, minHeight: 'auto' };
|
|
1379
|
+
function make$3(create, key, type, role, style, props, label) {
|
|
1380
|
+
return makeCompositePart(create, { key, type, props, elementStyle: style }, role, label, role ? PART_BASE$2[role] : undefined);
|
|
1349
1381
|
}
|
|
1350
1382
|
/** One of the trigger's two words: shown while the disclosure is closed (`more`) or open (`less`). */
|
|
1351
1383
|
function buildWord(create, role, words, rootId) {
|
|
1352
|
-
const node = make$
|
|
1384
|
+
const node = make$3(create, role === DisclosureRole.More ? 'more' : 'less', Type$4.Text, role, Style$4.Word, { content: words, tag: 'span' }, 'Text');
|
|
1353
1385
|
node.stateStyles = { ...(node.stateStyles ?? {}), [ITEM_OPEN_STATE]: { display: role === DisclosureRole.More ? HIDDEN : SHOWN } };
|
|
1354
1386
|
if (rootId)
|
|
1355
1387
|
node.stateTriggers = { ...(node.stateTriggers ?? {}), [ITEM_OPEN_STATE]: rootId };
|
|
@@ -1358,18 +1390,18 @@ function buildWord(create, role, words, rootId) {
|
|
|
1358
1390
|
/** A fresh Disclosure: "Read more" opens the text below and reads "Read less" while it is open; closed by default. */
|
|
1359
1391
|
function buildDisclosureRecipe(create, options = {}) {
|
|
1360
1392
|
const words = { ...DISCLOSURE_WORDS, ...(options.words ?? {}) };
|
|
1361
|
-
const root = make$
|
|
1393
|
+
const root = make$3(create, 'disclosure', Type$4.Container, DisclosureRole.Disclosure, Style$4.Root, {}, 'Disclosure');
|
|
1362
1394
|
root.itemKey = DISCLOSURE_ITEM_KEY;
|
|
1363
1395
|
writeProp(root, CompositeBehaviourTrait.DefaultOn, options.open ? AccordionDefaultOn.First : AccordionDefaultOn.None);
|
|
1364
1396
|
const icon = options.icon ?? AccordionIcon.Chevron;
|
|
1365
|
-
const trigger = make$
|
|
1397
|
+
const trigger = make$3(create, 'trigger', Type$4.LinkedContainer, DisclosureRole.Trigger, Style$4.Trigger, {}, 'Toggle');
|
|
1366
1398
|
trigger.children = [
|
|
1367
1399
|
buildWord(create, DisclosureRole.More, words.more, root.id),
|
|
1368
1400
|
buildWord(create, DisclosureRole.Less, words.less, root.id),
|
|
1369
1401
|
...(icon === AccordionIcon.None ? [] : [buildCompositeIcon(create, 'icon', icon, root.id, DisclosureRole.Icon)]),
|
|
1370
1402
|
];
|
|
1371
|
-
const panel = make$
|
|
1372
|
-
const body = make$
|
|
1403
|
+
const panel = make$3(create, 'panel', Type$4.Container, DisclosureRole.Panel, Style$4.Panel, {}, 'Panel');
|
|
1404
|
+
const body = make$3(create, 'body', Type$4.Text, null, Style$4.Body, { content: words.body, tag: 'p' }, 'Text');
|
|
1373
1405
|
writeStyles(body, BODY_BASE$1);
|
|
1374
1406
|
panel.children = [body];
|
|
1375
1407
|
root.children = [trigger, panel];
|
|
@@ -1532,7 +1564,7 @@ const CARD_WORDS = {
|
|
|
1532
1564
|
};
|
|
1533
1565
|
/** The breakpoint whose layer stacks an "Image side" card (Breakpoint.Mobile, ≤767px). */
|
|
1534
1566
|
const CARD_STACK_BREAKPOINT = 'mobile';
|
|
1535
|
-
var Type$
|
|
1567
|
+
var Type$3;
|
|
1536
1568
|
(function (Type) {
|
|
1537
1569
|
Type["Container"] = "Container";
|
|
1538
1570
|
Type["Image"] = "Image";
|
|
@@ -1540,8 +1572,8 @@ var Type$2;
|
|
|
1540
1572
|
Type["Text"] = "Text";
|
|
1541
1573
|
Type["Icon"] = "Icon";
|
|
1542
1574
|
Type["ButtonBlock"] = "ButtonBlock";
|
|
1543
|
-
})(Type$
|
|
1544
|
-
var Style$
|
|
1575
|
+
})(Type$3 || (Type$3 = {}));
|
|
1576
|
+
var Style$3;
|
|
1545
1577
|
(function (Style) {
|
|
1546
1578
|
Style["Card"] = "card";
|
|
1547
1579
|
Style["Box"] = "box";
|
|
@@ -1554,7 +1586,7 @@ var Style$2;
|
|
|
1554
1586
|
Style["Ghost"] = "button-ghost";
|
|
1555
1587
|
Style["Icon"] = "icon";
|
|
1556
1588
|
Style["Scrim"] = "media-scrim";
|
|
1557
|
-
})(Style$
|
|
1589
|
+
})(Style$3 || (Style$3 = {}));
|
|
1558
1590
|
var Scheme;
|
|
1559
1591
|
(function (Scheme) {
|
|
1560
1592
|
Scheme["Default"] = "default";
|
|
@@ -1564,11 +1596,11 @@ const LINK_TYPE = 'linkType';
|
|
|
1564
1596
|
const SAVE_ICON = 'ph ph-bookmark-simple';
|
|
1565
1597
|
/** A value an element style writes — read from the contract, never retyped. */
|
|
1566
1598
|
const styleValue = (style, trait) => String(elementStyleById(style)?.base[trait] ?? '');
|
|
1567
|
-
const CARD_PAD = styleValue(Style$
|
|
1568
|
-
const IMAGE_RADIUS = styleValue(Style$
|
|
1569
|
-
const TITLE_SIZE = styleValue(Style$
|
|
1599
|
+
const CARD_PAD = styleValue(Style$3.Card, 'padding');
|
|
1600
|
+
const IMAGE_RADIUS = styleValue(Style$3.Media, 'borderRadius');
|
|
1601
|
+
const TITLE_SIZE = styleValue(Style$3.Title, 'fontSize');
|
|
1570
1602
|
const LEAD_SIZE = 'var(--iox-size-lead)';
|
|
1571
|
-
const ZERO_BOX$
|
|
1603
|
+
const ZERO_BOX$2 = '0px 0px 0px 0px';
|
|
1572
1604
|
/** What every card part holds in every layout — the recipe's box. */
|
|
1573
1605
|
const CARD_BASE = {
|
|
1574
1606
|
// No height: a card is a grid or flex item, and the cell's stretch is what makes a row's cards equal.
|
|
@@ -1576,25 +1608,25 @@ const CARD_BASE = {
|
|
|
1576
1608
|
[CardRole.Card]: { display: 'flex', gap: '14px', width: '100%', minWidth: '0px', overflow: 'hidden', position: 'relative' },
|
|
1577
1609
|
[CardRole.Media]: { display: 'block', width: '100%', minHeight: 'auto', flexGrow: '0', flexShrink: '0' },
|
|
1578
1610
|
[CardRole.Content]: { display: 'flex', flexDirection: 'column', gap: '8px', width: '100%', minWidth: '0px', minHeight: 'auto' },
|
|
1579
|
-
[CardRole.Eyebrow]: { margin: ZERO_BOX$
|
|
1580
|
-
[CardRole.Title]: { margin: ZERO_BOX$
|
|
1581
|
-
[CardRole.Text]: { margin: ZERO_BOX$
|
|
1611
|
+
[CardRole.Eyebrow]: { margin: ZERO_BOX$2, padding: ZERO_BOX$2, minHeight: 'auto' },
|
|
1612
|
+
[CardRole.Title]: { margin: ZERO_BOX$2 },
|
|
1613
|
+
[CardRole.Text]: { margin: ZERO_BOX$2, padding: ZERO_BOX$2, minHeight: 'auto' },
|
|
1582
1614
|
// Buttons pinned to the bottom (board 8 Q: equal heights + buttons pinned): the content column grows,
|
|
1583
1615
|
// and the row's auto top margin takes the slack.
|
|
1584
1616
|
[CardRole.Actions]: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: '8px', margin: 'auto 0px 0px 0px', padding: '6px 0px 0px 0px', width: '100%', minHeight: 'auto' },
|
|
1585
1617
|
};
|
|
1586
1618
|
const IMAGE_BASE$2 = { display: 'block', width: '100%', maxWidth: '100%' };
|
|
1587
1619
|
/** A button's label and a pill's words: inherit the type and ink of what holds them. */
|
|
1588
|
-
const LABEL_BASE$
|
|
1620
|
+
const LABEL_BASE$2 = { width: 'auto', minHeight: 'auto', padding: ZERO_BOX$2, margin: ZERO_BOX$2 };
|
|
1589
1621
|
const INLINE_ICON = { fontSize: '1em' };
|
|
1590
1622
|
const IN_FLOW = { position: 'relative', top: 'auto', right: 'auto', bottom: 'auto', left: 'auto' };
|
|
1591
|
-
const GROWING_CONTENT = { position: 'static', flexGrow: '1', flexShrink: '1', flexBasis: '0%', padding: ZERO_BOX$
|
|
1623
|
+
const GROWING_CONTENT = { position: 'static', flexGrow: '1', flexShrink: '1', flexBasis: '0%', padding: ZERO_BOX$2 };
|
|
1592
1624
|
const LAYOUTS = {
|
|
1593
1625
|
[CardLayout.Top]: {
|
|
1594
1626
|
card: { flexDirection: 'column', alignItems: 'stretch', justifyContent: 'flex-start', minHeight: 'auto', padding: CARD_PAD },
|
|
1595
1627
|
media: { ...IN_FLOW, flexBasis: 'auto' },
|
|
1596
1628
|
image: { aspectRatio: '4 / 3', height: 'auto', minHeight: 'auto', borderRadius: IMAGE_RADIUS },
|
|
1597
|
-
content: GROWING_CONTENT, contentStyle: Style$
|
|
1629
|
+
content: GROWING_CONTENT, contentStyle: Style$3.Box, contentScheme: Scheme.Default,
|
|
1598
1630
|
title: { fontSize: TITLE_SIZE },
|
|
1599
1631
|
badgeOnMedia: true,
|
|
1600
1632
|
},
|
|
@@ -1602,7 +1634,7 @@ const LAYOUTS = {
|
|
|
1602
1634
|
card: { flexDirection: 'row', alignItems: 'stretch', justifyContent: 'flex-start', minHeight: 'auto', padding: CARD_PAD },
|
|
1603
1635
|
media: { ...IN_FLOW, flexBasis: '42%' },
|
|
1604
1636
|
image: { aspectRatio: 'auto', height: '100%', minHeight: '180px', borderRadius: IMAGE_RADIUS },
|
|
1605
|
-
content: GROWING_CONTENT, contentStyle: Style$
|
|
1637
|
+
content: GROWING_CONTENT, contentStyle: Style$3.Box, contentScheme: Scheme.Default,
|
|
1606
1638
|
title: { fontSize: TITLE_SIZE },
|
|
1607
1639
|
badgeOnMedia: true,
|
|
1608
1640
|
stack: {
|
|
@@ -1612,11 +1644,11 @@ const LAYOUTS = {
|
|
|
1612
1644
|
},
|
|
1613
1645
|
},
|
|
1614
1646
|
[CardLayout.Overlay]: {
|
|
1615
|
-
card: { flexDirection: 'column', alignItems: 'stretch', justifyContent: 'flex-end', minHeight: '340px', padding: ZERO_BOX$
|
|
1647
|
+
card: { flexDirection: 'column', alignItems: 'stretch', justifyContent: 'flex-end', minHeight: '340px', padding: ZERO_BOX$2 },
|
|
1616
1648
|
media: { position: 'absolute', top: '0px', right: '0px', bottom: '0px', left: '0px', flexBasis: 'auto' },
|
|
1617
1649
|
image: { aspectRatio: 'auto', height: '100%', minHeight: 'auto', borderRadius: '0px' },
|
|
1618
1650
|
content: { position: 'relative', flexGrow: '0', flexShrink: '0', flexBasis: 'auto', padding: `72px ${CARD_PAD} ${CARD_PAD} ${CARD_PAD}` },
|
|
1619
|
-
contentStyle: Style$
|
|
1651
|
+
contentStyle: Style$3.Scrim, contentScheme: Scheme.Inverse,
|
|
1620
1652
|
title: { fontSize: TITLE_SIZE },
|
|
1621
1653
|
badgeOnMedia: false,
|
|
1622
1654
|
},
|
|
@@ -1624,7 +1656,7 @@ const LAYOUTS = {
|
|
|
1624
1656
|
card: { flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-start', minHeight: 'auto', padding: CARD_PAD },
|
|
1625
1657
|
media: { ...IN_FLOW, flexBasis: '72px' },
|
|
1626
1658
|
image: { aspectRatio: '1 / 1', height: 'auto', minHeight: 'auto', borderRadius: IMAGE_RADIUS },
|
|
1627
|
-
content: GROWING_CONTENT, contentStyle: Style$
|
|
1659
|
+
content: GROWING_CONTENT, contentStyle: Style$3.Box, contentScheme: Scheme.Default,
|
|
1628
1660
|
title: { fontSize: LEAD_SIZE },
|
|
1629
1661
|
badgeOnMedia: false,
|
|
1630
1662
|
partsOff: [CardPart.Text, CardPart.Actions],
|
|
@@ -1635,7 +1667,7 @@ const STACK_KEYS = {
|
|
|
1635
1667
|
media: Object.keys(LAYOUTS[CardLayout.Side].stack.media),
|
|
1636
1668
|
image: Object.keys(LAYOUTS[CardLayout.Side].stack.image),
|
|
1637
1669
|
};
|
|
1638
|
-
function make$
|
|
1670
|
+
function make$2(create, key, type, role, elementStyle, props = {}, label) {
|
|
1639
1671
|
const node = create({ key, type, props, elementStyle });
|
|
1640
1672
|
if (role)
|
|
1641
1673
|
node.role = role;
|
|
@@ -1645,33 +1677,33 @@ function make$1(create, key, type, role, elementStyle, props = {}, label) {
|
|
|
1645
1677
|
return node;
|
|
1646
1678
|
}
|
|
1647
1679
|
function buttonLabel(create, key, words) {
|
|
1648
|
-
const label = make$
|
|
1649
|
-
writeStyles(label, LABEL_BASE$
|
|
1680
|
+
const label = make$2(create, key, Type$3.Text, null, Style$3.Label, { content: words, tag: 'span' });
|
|
1681
|
+
writeStyles(label, LABEL_BASE$2);
|
|
1650
1682
|
return label;
|
|
1651
1683
|
}
|
|
1652
1684
|
/** Each optional part (and the locked title) as the recipe builds it. */
|
|
1653
1685
|
const PART_BUILDERS = {
|
|
1654
1686
|
[CardPart.Media]: (create) => {
|
|
1655
|
-
const media = make$
|
|
1656
|
-
const image = make$
|
|
1687
|
+
const media = make$2(create, 'media', Type$3.Container, CardRole.Media, Style$3.Box, {}, 'Media');
|
|
1688
|
+
const image = make$2(create, 'image', Type$3.Image, null, Style$3.Media, { src: '', alt: '' });
|
|
1657
1689
|
writeStyles(image, IMAGE_BASE$2);
|
|
1658
1690
|
media.children = [image];
|
|
1659
1691
|
return media;
|
|
1660
1692
|
},
|
|
1661
|
-
[CardPart.Badge]: (create, words, tone) => {
|
|
1662
|
-
const badge = buildBadge(create, { tone, text: words.badge, key: 'badge' });
|
|
1693
|
+
[CardPart.Badge]: (create, words, tone, badgeIcon) => {
|
|
1694
|
+
const badge = buildBadge(create, { tone, text: words.badge, key: 'badge', ...(badgeIcon ? { icon: badgeIcon } : {}) });
|
|
1663
1695
|
badge.role = CardRole.Badge;
|
|
1664
1696
|
badge.label = 'Badge';
|
|
1665
1697
|
return badge;
|
|
1666
1698
|
},
|
|
1667
|
-
[CardPart.Eyebrow]: (create, words) => make$
|
|
1668
|
-
[CardPart.Text]: (create, words) => make$
|
|
1699
|
+
[CardPart.Eyebrow]: (create, words) => make$2(create, 'eyebrow', Type$3.Text, CardRole.Eyebrow, Style$3.Eyebrow, { content: words.eyebrow, tag: 'p' }, 'Eyebrow'),
|
|
1700
|
+
[CardPart.Text]: (create, words) => make$2(create, 'text', Type$3.Text, CardRole.Text, Style$3.Body, { content: words.text, tag: 'p' }, 'Text'),
|
|
1669
1701
|
[CardPart.Actions]: (create, words) => {
|
|
1670
|
-
const actions = make$
|
|
1671
|
-
const primary = make$
|
|
1702
|
+
const actions = make$2(create, 'actions', Type$3.Container, CardRole.Actions, Style$3.Box, {}, 'Buttons');
|
|
1703
|
+
const primary = make$2(create, 'primary', Type$3.ButtonBlock, null, Style$3.Primary);
|
|
1672
1704
|
primary.children = [buttonLabel(create, 'primary-label', words.primary)];
|
|
1673
|
-
const ghost = make$
|
|
1674
|
-
const icon = make$
|
|
1705
|
+
const ghost = make$2(create, 'secondary', Type$3.ButtonBlock, null, Style$3.Ghost);
|
|
1706
|
+
const icon = make$2(create, 'secondary-icon', Type$3.Icon, null, Style$3.Icon, { iconClass: SAVE_ICON });
|
|
1675
1707
|
writeStyles(icon, INLINE_ICON);
|
|
1676
1708
|
ghost.children = [icon, buttonLabel(create, 'secondary-label', words.secondary)];
|
|
1677
1709
|
actions.children = [primary, ghost];
|
|
@@ -1679,15 +1711,15 @@ const PART_BUILDERS = {
|
|
|
1679
1711
|
},
|
|
1680
1712
|
};
|
|
1681
1713
|
function buildTitle(create, words) {
|
|
1682
|
-
return make$
|
|
1714
|
+
return make$2(create, 'title', Type$3.Heading, CardRole.Title, Style$3.Title, { content: words.title, level: 'h3', [LINK_TYPE]: LinkType.None }, 'Title');
|
|
1683
1715
|
}
|
|
1684
1716
|
/** A fresh Card v2: every part a node with its role and element style, laid out by `options.layout`. */
|
|
1685
1717
|
function buildCardRecipe(create, options = {}) {
|
|
1686
1718
|
const words = { ...CARD_WORDS, ...(options.words ?? {}) };
|
|
1687
1719
|
const tone = options.badgeTone ?? BadgeTone.Accent;
|
|
1688
1720
|
const on = (part) => options.parts?.[part] !== false;
|
|
1689
|
-
const card = make$
|
|
1690
|
-
const content = make$
|
|
1721
|
+
const card = make$2(create, 'card', Type$3.Container, CardRole.Card, Style$3.Card, {}, 'Card');
|
|
1722
|
+
const content = make$2(create, 'content', Type$3.Container, CardRole.Content, Style$3.Box, {}, 'Content');
|
|
1691
1723
|
content.children = [
|
|
1692
1724
|
...(on(CardPart.Eyebrow) ? [PART_BUILDERS[CardPart.Eyebrow](create, words, tone)] : []),
|
|
1693
1725
|
buildTitle(create, words),
|
|
@@ -1696,7 +1728,7 @@ function buildCardRecipe(create, options = {}) {
|
|
|
1696
1728
|
];
|
|
1697
1729
|
card.children = [...(on(CardPart.Media) ? [PART_BUILDERS[CardPart.Media](create, words, tone)] : []), content];
|
|
1698
1730
|
if (on(CardPart.Badge))
|
|
1699
|
-
placeBadge(card, PART_BUILDERS[CardPart.Badge](create, words, tone), true);
|
|
1731
|
+
placeBadge(card, PART_BUILDERS[CardPart.Badge](create, words, tone, options.badgeIcon), true);
|
|
1700
1732
|
applyCardLayout(card, options.layout ?? CardLayout.Top, options);
|
|
1701
1733
|
return card;
|
|
1702
1734
|
}
|
|
@@ -1718,7 +1750,7 @@ function cardRootOf(node, path) {
|
|
|
1718
1750
|
function cardPartsOf(card) {
|
|
1719
1751
|
return Object.fromEntries(CARD_PARTS.map(part => [part, !!findByRole(card, CARD_PART_ROLE[part])]));
|
|
1720
1752
|
}
|
|
1721
|
-
const imageOf = (media) => media?.children?.find(c => c.type === Type$
|
|
1753
|
+
const imageOf = (media) => media?.children?.find(c => c.type === Type$3.Image);
|
|
1722
1754
|
/** Put the badge where the layout says: the Media's top corner, or the head of the content column
|
|
1723
1755
|
* (Image behind text, Compact, or no Media at all). The corner's insets are logical, in iox-design.css
|
|
1724
1756
|
* (mirrored in RTL); `position` is the node's own, because the editing canvas forces every node's
|
|
@@ -2001,17 +2033,17 @@ function sliderSlides(children) {
|
|
|
2001
2033
|
return (children ?? []).filter(c => !isSliderPart(c));
|
|
2002
2034
|
}
|
|
2003
2035
|
// ── the parts' recipe ───────────────────────────────────────────────────────────────────────────────
|
|
2004
|
-
var Type$
|
|
2036
|
+
var Type$2;
|
|
2005
2037
|
(function (Type) {
|
|
2006
2038
|
Type["ButtonBlock"] = "ButtonBlock";
|
|
2007
2039
|
Type["Icon"] = "Icon";
|
|
2008
2040
|
Type["Container"] = "Container";
|
|
2009
|
-
})(Type$
|
|
2010
|
-
var Style$
|
|
2041
|
+
})(Type$2 || (Type$2 = {}));
|
|
2042
|
+
var Style$2;
|
|
2011
2043
|
(function (Style) {
|
|
2012
2044
|
Style["Control"] = "button-secondary";
|
|
2013
2045
|
Style["Icon"] = "icon";
|
|
2014
|
-
})(Style$
|
|
2046
|
+
})(Style$2 || (Style$2 = {}));
|
|
2015
2047
|
/** Each part's glyph (Phosphor) and its default accessible name. */
|
|
2016
2048
|
const SLIDER_PART_GLYPH = {
|
|
2017
2049
|
[SliderRole.Pause]: 'ph ph-pause',
|
|
@@ -2053,7 +2085,7 @@ function legacyLookOf(slider) {
|
|
|
2053
2085
|
/** One part, from the recipe (board 6: `button-secondary` controls, token dots) or in a migrated slider's legacy look. */
|
|
2054
2086
|
function buildSliderPart(create, role, legacy) {
|
|
2055
2087
|
if (role === SliderRole.Pagination) {
|
|
2056
|
-
const dots = makeCompositePart(create, { key: 'pagination', type: Type$
|
|
2088
|
+
const dots = makeCompositePart(create, { key: 'pagination', type: Type$2.Container, props: {}, elementStyle: NO_ELEMENT_STYLE }, role, PART_LABEL[role], legacy?.paginationColor ? { ...PAGINATION_BASE, color: legacy.paginationColor } : PAGINATION_BASE);
|
|
2057
2089
|
dots.children = [];
|
|
2058
2090
|
return dots;
|
|
2059
2091
|
}
|
|
@@ -2069,9 +2101,9 @@ function buildSliderPart(create, role, legacy) {
|
|
|
2069
2101
|
const props = { tag: 'button', type: 'button' };
|
|
2070
2102
|
if (role !== SliderRole.Pause)
|
|
2071
2103
|
props[SLIDER_NAME_TRAIT] = role === SliderRole.Prev ? SLIDER_WORDS.en.prev : SLIDER_WORDS.en.next;
|
|
2072
|
-
const button = makeCompositePart(create, { key, type: Type$
|
|
2104
|
+
const button = makeCompositePart(create, { key, type: Type$2.ButtonBlock, props, elementStyle: legacy ? NO_ELEMENT_STYLE : Style$2.Control }, role, PART_LABEL[role], base);
|
|
2073
2105
|
const iconBase = legacy ? { ...ICON_BASE, fontSize: `${Math.round((legacy.navigationSize ?? 44) * 0.6)}px` } : ICON_BASE;
|
|
2074
|
-
const icon = makeCompositePart(create, { key: `${key}-icon`, type: Type$
|
|
2106
|
+
const icon = makeCompositePart(create, { key: `${key}-icon`, type: Type$2.Icon, props: { iconClass: SLIDER_PART_GLYPH[role] }, elementStyle: legacy ? NO_ELEMENT_STYLE : Style$2.Icon }, null, 'Icon', iconBase);
|
|
2075
2107
|
button.children = [icon];
|
|
2076
2108
|
return button;
|
|
2077
2109
|
}
|
|
@@ -2263,6 +2295,11 @@ const COMPOSITE_ROLE_SPECS = {
|
|
|
2263
2295
|
[TabsRole.Panels]: { parent: TabsRole.Tabs, locked: true, accepts: [TabsRole.Panel] },
|
|
2264
2296
|
[TabsRole.Trigger]: { parent: TabsRole.List, pinned: true },
|
|
2265
2297
|
[TabsRole.Panel]: { parent: TabsRole.Panels, pinned: true },
|
|
2298
|
+
// CC4b — a Segmented control holds only its options (each may be deleted — two stay — but never moved out); an
|
|
2299
|
+
// option's saving badge is optional (Option ▸ Saving badge) and stays in its option.
|
|
2300
|
+
[SegmentedRole.Segmented]: { parent: null, accepts: [SegmentedRole.Option] },
|
|
2301
|
+
[SegmentedRole.Option]: { parent: SegmentedRole.Segmented, pinned: true },
|
|
2302
|
+
[SegmentedRole.Badge]: { parent: SegmentedRole.Option, pinned: true },
|
|
2266
2303
|
// CC3 — a Slider's controls are its own direct children (Swiper is handed them). The pause control stays while
|
|
2267
2304
|
// Autoplay is on (WCAG 2.2.2): the Autoplay switch removes it, never Delete. The arrows and the dots follow
|
|
2268
2305
|
// their switches and may be deleted.
|
|
@@ -2480,7 +2517,7 @@ const SLIDE_TO_ROW = 'row';
|
|
|
2480
2517
|
const ROW_INDEX_ATTRIBUTE = 'data-iox-row-idx';
|
|
2481
2518
|
const LIGHTBOX_LABEL = 'Lightbox';
|
|
2482
2519
|
const DIALOG = 'Dialog';
|
|
2483
|
-
const REPEATER = 'Repeater';
|
|
2520
|
+
const REPEATER$1 = 'Repeater';
|
|
2484
2521
|
const SOURCE = 'source';
|
|
2485
2522
|
const SOURCE_PATH = 'sourcePath';
|
|
2486
2523
|
const ZERO = '0px 0px 0px 0px';
|
|
@@ -2573,7 +2610,7 @@ function buildLightboxRecipe(create, options = {}) {
|
|
|
2573
2610
|
const lastSegment = (path) => (path ? String(path).split('.').filter(Boolean).pop() ?? '' : '');
|
|
2574
2611
|
/** What a Repeater (its `source` binding) or a bound Slider (its `source` / `sourcePath` traits) repeats over. */
|
|
2575
2612
|
function repeatSourceOf(node) {
|
|
2576
|
-
if (!node || (node.type !== REPEATER && node.type !== SLIDER_TYPE))
|
|
2613
|
+
if (!node || (node.type !== REPEATER$1 && node.type !== SLIDER_TYPE))
|
|
2577
2614
|
return null;
|
|
2578
2615
|
const binding = (node.bindings ?? []).find(b => b?.prop === SOURCE);
|
|
2579
2616
|
if (binding?.source)
|
|
@@ -2674,15 +2711,15 @@ const TABS_WORDS = [
|
|
|
2674
2711
|
{ label: 'Third tab', text: 'Say what this tab is about in a sentence or two. Its picture sits beside it.' },
|
|
2675
2712
|
];
|
|
2676
2713
|
const TABS_NEW_TAB_WORDS = { label: 'New tab', text: 'Say what this tab is about in a sentence or two. Its picture sits beside it.' };
|
|
2677
|
-
var Type;
|
|
2714
|
+
var Type$1;
|
|
2678
2715
|
(function (Type) {
|
|
2679
2716
|
Type["Container"] = "Container";
|
|
2680
2717
|
Type["ButtonBlock"] = "ButtonBlock";
|
|
2681
2718
|
Type["Text"] = "Text";
|
|
2682
2719
|
Type["Image"] = "Image";
|
|
2683
2720
|
Type["Repeater"] = "Repeater";
|
|
2684
|
-
})(Type || (Type = {}));
|
|
2685
|
-
var Style;
|
|
2721
|
+
})(Type$1 || (Type$1 = {}));
|
|
2722
|
+
var Style$1;
|
|
2686
2723
|
(function (Style) {
|
|
2687
2724
|
Style["Root"] = "box";
|
|
2688
2725
|
Style["List"] = "tab-list";
|
|
@@ -2692,12 +2729,12 @@ var Style;
|
|
|
2692
2729
|
Style["Panel"] = "box";
|
|
2693
2730
|
Style["Media"] = "media";
|
|
2694
2731
|
Style["Body"] = "body";
|
|
2695
|
-
})(Style || (Style = {}));
|
|
2696
|
-
const ZERO_BOX = '0px 0px 0px 0px';
|
|
2732
|
+
})(Style$1 || (Style$1 = {}));
|
|
2733
|
+
const ZERO_BOX$1 = '0px 0px 0px 0px';
|
|
2697
2734
|
/** The list's hairline, as its element style writes it — read from the contract, never retyped. */
|
|
2698
|
-
const LIST_EDGE = String(elementStyleById(Style.List)?.base['borderWidth'] ?? ZERO_BOX);
|
|
2735
|
+
const LIST_EDGE = String(elementStyleById(Style$1.List)?.base['borderWidth'] ?? ZERO_BOX$1);
|
|
2699
2736
|
/** What each part holds — layout only; its LOOK is its element style. The list WRAPS at narrow widths (board 3). */
|
|
2700
|
-
const PART_BASE = {
|
|
2737
|
+
const PART_BASE$1 = {
|
|
2701
2738
|
[TabsRole.Tabs]: { display: 'flex', width: '100%', minHeight: 'auto', height: 'auto' },
|
|
2702
2739
|
[TabsRole.List]: { display: 'flex', gap: '4px', minHeight: 'auto', height: 'auto' },
|
|
2703
2740
|
[TabsRole.Trigger]: { width: 'auto', minHeight: 'auto', height: 'auto', cursor: 'pointer' },
|
|
@@ -2705,9 +2742,9 @@ const PART_BASE = {
|
|
|
2705
2742
|
// Board 3: a panel is any nodes at all — the recipe's is a picture beside its words.
|
|
2706
2743
|
[TabsRole.Panel]: { display: 'grid', gridTemplateColumns: '1fr 1.2fr', gap: '24px', alignItems: 'center', width: '100%', minHeight: 'auto', height: 'auto' },
|
|
2707
2744
|
};
|
|
2708
|
-
const LABEL_BASE = { width: 'auto', minHeight: 'auto', padding: ZERO_BOX, margin: ZERO_BOX };
|
|
2745
|
+
const LABEL_BASE$1 = { width: 'auto', minHeight: 'auto', padding: ZERO_BOX$1, margin: ZERO_BOX$1 };
|
|
2709
2746
|
const IMAGE_BASE = { display: 'block', width: '100%', maxWidth: '100%', aspectRatio: '4 / 3', height: 'auto', minHeight: 'auto' };
|
|
2710
|
-
const BODY_BASE = { margin: ZERO_BOX, padding: ZERO_BOX, minHeight: 'auto' };
|
|
2747
|
+
const BODY_BASE = { margin: ZERO_BOX$1, padding: ZERO_BOX$1, minHeight: 'auto' };
|
|
2711
2748
|
/** On a phone a panel's picture sits above its words. */
|
|
2712
2749
|
const PANEL_STACK = { gridTemplateColumns: '1fr' };
|
|
2713
2750
|
const ORIENTATION_LAYOUT = {
|
|
@@ -2717,42 +2754,42 @@ const ORIENTATION_LAYOUT = {
|
|
|
2717
2754
|
},
|
|
2718
2755
|
[TabsOrientation.Vertical]: {
|
|
2719
2756
|
root: { flexDirection: 'row', alignItems: 'flex-start', gap: '24px' },
|
|
2720
|
-
list: { flexDirection: 'column', flexWrap: 'nowrap', width: 'auto', flexShrink: '0', borderWidth: ZERO_BOX },
|
|
2757
|
+
list: { flexDirection: 'column', flexWrap: 'nowrap', width: 'auto', flexShrink: '0', borderWidth: ZERO_BOX$1 },
|
|
2721
2758
|
rootStack: { flexDirection: 'column', alignItems: 'stretch', gap: '20px' },
|
|
2722
2759
|
listStack: { width: '100%' },
|
|
2723
2760
|
},
|
|
2724
2761
|
};
|
|
2725
2762
|
const ROOT_STACK_KEYS = ['flexDirection', 'alignItems', 'gap'];
|
|
2726
2763
|
const LIST_STACK_KEYS = ['width'];
|
|
2727
|
-
function make(create, key, type, role, style, props = {}, label) {
|
|
2728
|
-
return makeCompositePart(create, { key, type, props, elementStyle: style }, role, label, role ? PART_BASE[role] : undefined);
|
|
2764
|
+
function make$1(create, key, type, role, style, props = {}, label) {
|
|
2765
|
+
return makeCompositePart(create, { key, type, props, elementStyle: style }, role, label, role ? PART_BASE$1[role] : undefined);
|
|
2729
2766
|
}
|
|
2730
2767
|
/** One tab and its panel, sharing `itemKey`. */
|
|
2731
2768
|
function buildTabPair(create, words, itemKey) {
|
|
2732
|
-
const tab = make(create, `${itemKey}-tab`, Type.ButtonBlock, TabsRole.Trigger, Style.Tab, {}, 'Tab');
|
|
2769
|
+
const tab = make$1(create, `${itemKey}-tab`, Type$1.ButtonBlock, TabsRole.Trigger, Style$1.Tab, {}, 'Tab');
|
|
2733
2770
|
tab.itemKey = itemKey;
|
|
2734
|
-
const label = make(create, `${itemKey}-label`, Type.Text, null, Style.Label, { content: words.label, tag: 'span' }, 'Text');
|
|
2735
|
-
writeStyles(label, LABEL_BASE);
|
|
2771
|
+
const label = make$1(create, `${itemKey}-label`, Type$1.Text, null, Style$1.Label, { content: words.label, tag: 'span' }, 'Text');
|
|
2772
|
+
writeStyles(label, LABEL_BASE$1);
|
|
2736
2773
|
tab.children = [label];
|
|
2737
|
-
const panel = make(create, `${itemKey}-panel`, Type.Container, TabsRole.Panel, Style.Panel, {}, 'Panel');
|
|
2774
|
+
const panel = make$1(create, `${itemKey}-panel`, Type$1.Container, TabsRole.Panel, Style$1.Panel, {}, 'Panel');
|
|
2738
2775
|
panel.itemKey = itemKey;
|
|
2739
2776
|
writeLayerStyles(panel, TABS_STACK_BREAKPOINT, PANEL_STACK);
|
|
2740
|
-
const image = make(create, `${itemKey}-image`, Type.Image, null, Style.Media, { src: '', alt: '' }, 'Image');
|
|
2777
|
+
const image = make$1(create, `${itemKey}-image`, Type$1.Image, null, Style$1.Media, { src: '', alt: '' }, 'Image');
|
|
2741
2778
|
writeStyles(image, IMAGE_BASE);
|
|
2742
|
-
const text = make(create, `${itemKey}-text`, Type.Text, null, Style.Body, { content: words.text, tag: 'p' }, 'Text');
|
|
2779
|
+
const text = make$1(create, `${itemKey}-text`, Type$1.Text, null, Style$1.Body, { content: words.text, tag: 'p' }, 'Text');
|
|
2743
2780
|
writeStyles(text, BODY_BASE);
|
|
2744
2781
|
panel.children = [image, text];
|
|
2745
2782
|
return { tab, panel };
|
|
2746
2783
|
}
|
|
2747
2784
|
/** A fresh Tabs: three tabs, the first on, activated by a click, the list across the top (board 3). */
|
|
2748
2785
|
function buildTabsRecipe(create, options = {}) {
|
|
2749
|
-
const root = make(create, 'tabs', Type.Container, TabsRole.Tabs, Style.Root, {}, 'Tabs');
|
|
2786
|
+
const root = make$1(create, 'tabs', Type$1.Container, TabsRole.Tabs, Style$1.Root, {}, 'Tabs');
|
|
2750
2787
|
writeProp(root, TabsBehaviourTrait.ActivateOn, options.activateOn ?? TabsActivateOn.Click);
|
|
2751
2788
|
writeProp(root, TabsBehaviourTrait.Orientation, options.orientation ?? TabsOrientation.Horizontal);
|
|
2752
2789
|
if (options.label)
|
|
2753
2790
|
writeProp(root, TabsBehaviourTrait.Label, options.label);
|
|
2754
|
-
const list = make(create, 'list', Type.Container, TabsRole.List, Style.List, {}, 'Tab list');
|
|
2755
|
-
const panels = make(create, 'panels', Type.Container, TabsRole.Panels, Style.Panels, {}, 'Panels');
|
|
2791
|
+
const list = make$1(create, 'list', Type$1.Container, TabsRole.List, Style$1.List, {}, 'Tab list');
|
|
2792
|
+
const panels = make$1(create, 'panels', Type$1.Container, TabsRole.Panels, Style$1.Panels, {}, 'Panels');
|
|
2756
2793
|
const pairs = (options.tabs ?? TABS_WORDS).map((w, i) => buildTabPair(create, w, `k${i + 1}`));
|
|
2757
2794
|
list.children = pairs.map(p => p.tab);
|
|
2758
2795
|
panels.children = pairs.map(p => p.panel);
|
|
@@ -2782,7 +2819,7 @@ function tabsPanels(root) {
|
|
|
2782
2819
|
}
|
|
2783
2820
|
/** True when the Tabs takes its tabs from content (its list is a Repeater, R3). */
|
|
2784
2821
|
function isTabsFromContent(root) {
|
|
2785
|
-
return !!root && tabsListOf(root)?.type === Type.Repeater;
|
|
2822
|
+
return !!root && tabsListOf(root)?.type === Type$1.Repeater;
|
|
2786
2823
|
}
|
|
2787
2824
|
/** The panel paired with `tab` (the same key), or undefined. From content the one template panel pairs the template tab. */
|
|
2788
2825
|
function tabPanelOf(root, tab) {
|
|
@@ -2807,15 +2844,15 @@ function tabsPartnerOf(root, node) {
|
|
|
2807
2844
|
/** The words a tab shows: its first Text's. */
|
|
2808
2845
|
function tabLabelNodeOf(tab) {
|
|
2809
2846
|
let hit;
|
|
2810
|
-
walkComposite(tab, n => { if (!hit && n !== tab && n.type === Type.Text)
|
|
2847
|
+
walkComposite(tab, n => { if (!hit && n !== tab && n.type === Type$1.Text)
|
|
2811
2848
|
hit = n; });
|
|
2812
2849
|
return hit;
|
|
2813
2850
|
}
|
|
2814
|
-
const stripTags = (v) => String(v ?? '').replace(/<[^>]*>/g, '').trim();
|
|
2851
|
+
const stripTags$1 = (v) => String(v ?? '').replace(/<[^>]*>/g, '').trim();
|
|
2815
2852
|
/** A tab's name in the Items list, the layer tree and the item-action picker: its words. */
|
|
2816
2853
|
function tabLabel(tab) {
|
|
2817
2854
|
const label = tabLabelNodeOf(tab);
|
|
2818
|
-
return label ? stripTags(readProp(label, 'content')) : '';
|
|
2855
|
+
return label ? stripTags$1(readProp(label, 'content')) : '';
|
|
2819
2856
|
}
|
|
2820
2857
|
/** Items ▸ rename: the tab's words. */
|
|
2821
2858
|
function renameTab(tab, words) {
|
|
@@ -2825,14 +2862,14 @@ function renameTab(tab, words) {
|
|
|
2825
2862
|
/** The first Text inside a panel (the words From content binds to). */
|
|
2826
2863
|
function tabPanelTextOf(panel) {
|
|
2827
2864
|
let hit;
|
|
2828
|
-
walkComposite(panel, n => { if (!hit && n !== panel && n.type === Type.Text)
|
|
2865
|
+
walkComposite(panel, n => { if (!hit && n !== panel && n.type === Type$1.Text)
|
|
2829
2866
|
hit = n; });
|
|
2830
2867
|
return hit;
|
|
2831
2868
|
}
|
|
2832
2869
|
/** The first Image inside a panel. */
|
|
2833
2870
|
function tabPanelImageOf(panel) {
|
|
2834
2871
|
let hit;
|
|
2835
|
-
walkComposite(panel, n => { if (!hit && n.type === Type.Image)
|
|
2872
|
+
walkComposite(panel, n => { if (!hit && n.type === Type$1.Image)
|
|
2836
2873
|
hit = n; });
|
|
2837
2874
|
return hit;
|
|
2838
2875
|
}
|
|
@@ -2970,7 +3007,7 @@ function tabsItemTriggerIds(root) {
|
|
|
2970
3007
|
}
|
|
2971
3008
|
}
|
|
2972
3009
|
function tabsScope(rootId, behaviour, keys, label = '') {
|
|
2973
|
-
return { rootId, behaviour, keys, active: keys[0] ?? null, label };
|
|
3010
|
+
return { kind: CompositeKind.Tabs, rootId, behaviour, keys, active: keys[0] ?? null, label };
|
|
2974
3011
|
}
|
|
2975
3012
|
/** The item keys a manual Tabs pairs by — its tabs' own keys, in order (a tab with none by its index). */
|
|
2976
3013
|
function manualTabKeys(root) {
|
|
@@ -3148,6 +3185,351 @@ function tabsToManual(root, list, panels, rows, clone, resolve) {
|
|
|
3148
3185
|
return pairs;
|
|
3149
3186
|
}
|
|
3150
3187
|
|
|
3188
|
+
/**
|
|
3189
|
+
* THE SEGMENTED CONTROL (CC4b — composite K4 T32, pulled forward; complex-components board 10, APPROVED 2026-10-23 with
|
|
3190
|
+
* Q31–Q40 as recommended). One of a few, always one on, FIXED options (R3 / Q36). Not a component: a recipe of
|
|
3191
|
+
* ordinary nodes, each part carrying a role, so every option, its words and its optional badge are selectable on the
|
|
3192
|
+
* canvas, in the layer tree and the breadcrumb, and wear their own element styles (R2).
|
|
3193
|
+
*
|
|
3194
|
+
* Container `segmented` role segmented traits defaultOn (an option's key) · ariaLabel (its name)
|
|
3195
|
+
* └─ ButtonBlock `segmented-option` ×2–4 role segmented.option itemKey monthly · yearly … → <button role=radio>
|
|
3196
|
+
* ├─ Text `button-label` the words
|
|
3197
|
+
* └─ Container `badge-success` role segmented.badge (optional, off) › Icon + Text — a placeholder (a price claim)
|
|
3198
|
+
*
|
|
3199
|
+
* The ARIA shape is the APG RADIO GROUP (Q31): `role=radiogroup` + its accessible name on the root, `<button role=radio
|
|
3200
|
+
* aria-checked>` per option with a roving tabindex — one Tab stop; ← → follow the reading direction (mirrored under
|
|
3201
|
+
* RTL), ↑ ↓ the DOM order, every move wraps and turns the option on. It runs the EXCLUSIVE runtime the Tabs run
|
|
3202
|
+
* (`exclusive-runtime.ts`), on the builder canvas and both engine paths, from the attribute planners below. What follows
|
|
3203
|
+
* the control (a price) is an ordinary node OUTSIDE it styled "when its option is on" — the targeted `trigger-on`.
|
|
3204
|
+
*/
|
|
3205
|
+
var SegmentedTrait;
|
|
3206
|
+
(function (SegmentedTrait) {
|
|
3207
|
+
/** The option on at first paint — an option's key. */
|
|
3208
|
+
SegmentedTrait["DefaultOn"] = "defaultOn";
|
|
3209
|
+
/** The radio group's accessible name (`aria-label` on the root). Empty → an audit finding. */
|
|
3210
|
+
SegmentedTrait["Label"] = "ariaLabel";
|
|
3211
|
+
})(SegmentedTrait || (SegmentedTrait = {}));
|
|
3212
|
+
/** Q40: two options at least, four at most (past four the words no longer fit a phone row — a Select or Tabs then). */
|
|
3213
|
+
const SEGMENTED_MIN_OPTIONS = 2;
|
|
3214
|
+
const SEGMENTED_MAX_OPTIONS = 4;
|
|
3215
|
+
/** A fresh Segmented control's options (board 10: a billing period) — generic words, the author's to rename. */
|
|
3216
|
+
const SEGMENTED_WORDS = [
|
|
3217
|
+
{ key: 'monthly', label: 'Monthly' },
|
|
3218
|
+
{ key: 'yearly', label: 'Yearly' },
|
|
3219
|
+
];
|
|
3220
|
+
const SEGMENTED_NEW_OPTION_WORDS = 'New option';
|
|
3221
|
+
/** The name a fresh control carries (board 10). */
|
|
3222
|
+
const SEGMENTED_NAME = 'Billing period';
|
|
3223
|
+
/** Q33: the saving badge's words start as a PLACEHOLDER — a saving is a price claim, never invented. */
|
|
3224
|
+
const SEGMENTED_BADGE_WORDS = 'Yearly saving';
|
|
3225
|
+
const SEGMENTED_BADGE_TONE = BadgeTone.Success;
|
|
3226
|
+
var Type;
|
|
3227
|
+
(function (Type) {
|
|
3228
|
+
Type["Container"] = "Container";
|
|
3229
|
+
Type["ButtonBlock"] = "ButtonBlock";
|
|
3230
|
+
Type["Text"] = "Text";
|
|
3231
|
+
})(Type || (Type = {}));
|
|
3232
|
+
var Style;
|
|
3233
|
+
(function (Style) {
|
|
3234
|
+
Style["Root"] = "segmented";
|
|
3235
|
+
Style["Option"] = "segmented-option";
|
|
3236
|
+
Style["Label"] = "button-label";
|
|
3237
|
+
})(Style || (Style = {}));
|
|
3238
|
+
const ZERO_BOX = '0px 0px 0px 0px';
|
|
3239
|
+
/** What each part holds — layout only; its LOOK is its element style (the track's fill, border, radius and pad; the
|
|
3240
|
+
* option's ink, pad, radius and 44px target). The options sit in a flex row a gap token apart (board 10); each GROWS,
|
|
3241
|
+
* which changes nothing while the track hugs them and shares the row when the track is stretched (the pricing
|
|
3242
|
+
* pattern's phone layer: board 10's full-width control). */
|
|
3243
|
+
const PART_BASE = {
|
|
3244
|
+
[SegmentedRole.Segmented]: { display: 'inline-flex', flexDirection: 'row', alignItems: 'stretch', gap: 'var(--iox-segmented-gap)', width: 'auto', maxWidth: '100%', height: 'auto' },
|
|
3245
|
+
[SegmentedRole.Option]: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: '8px', width: 'auto', height: 'auto', cursor: 'pointer', flexGrow: '1' },
|
|
3246
|
+
};
|
|
3247
|
+
const LABEL_BASE = { width: 'auto', minHeight: 'auto', padding: ZERO_BOX, margin: ZERO_BOX };
|
|
3248
|
+
/** The badge inside an option keeps to the option's row (never its own line). */
|
|
3249
|
+
const BADGE_IN_OPTION = { alignSelf: 'center' };
|
|
3250
|
+
function make(create, key, type, role, style, props = {}, label) {
|
|
3251
|
+
return makeCompositePart(create, { key, type, props, elementStyle: style }, role, label, role ? PART_BASE[role] : undefined);
|
|
3252
|
+
}
|
|
3253
|
+
/** One option: a ButtonBlock carrying its key, holding its words. */
|
|
3254
|
+
function buildSegmentedOption(create, words) {
|
|
3255
|
+
const option = make(create, `${words.key}-option`, Type.ButtonBlock, SegmentedRole.Option, Style.Option, {}, 'Option');
|
|
3256
|
+
option.itemKey = words.key;
|
|
3257
|
+
const label = make(create, `${words.key}-label`, Type.Text, null, Style.Label, { content: words.label, tag: 'span' }, 'Text');
|
|
3258
|
+
writeStyles(label, LABEL_BASE);
|
|
3259
|
+
option.children = [label];
|
|
3260
|
+
return option;
|
|
3261
|
+
}
|
|
3262
|
+
/** A fresh Segmented control: Monthly · Yearly, Monthly on, named "Billing period" (board 10 ▸ Palette). */
|
|
3263
|
+
function buildSegmentedRecipe(create, options = {}) {
|
|
3264
|
+
const words = options.options ?? SEGMENTED_WORDS;
|
|
3265
|
+
const root = make(create, 'segmented', Type.Container, SegmentedRole.Segmented, Style.Root, {}, 'Segmented');
|
|
3266
|
+
root.children = words.map(w => buildSegmentedOption(create, w));
|
|
3267
|
+
writeProp(root, SegmentedTrait.DefaultOn, options.defaultOn && words.some(w => w.key === options.defaultOn) ? options.defaultOn : words[0]?.key ?? '');
|
|
3268
|
+
writeProp(root, SegmentedTrait.Label, options.label ?? SEGMENTED_NAME);
|
|
3269
|
+
return root;
|
|
3270
|
+
}
|
|
3271
|
+
// ── reading a tree ──────────────────────────────────────────────────────────────────────────────
|
|
3272
|
+
function isSegmentedRoot(node) {
|
|
3273
|
+
return node?.role === SegmentedRole.Segmented;
|
|
3274
|
+
}
|
|
3275
|
+
/** The options in order. */
|
|
3276
|
+
function segmentedOptions(root) {
|
|
3277
|
+
return (root?.children ?? []).filter(c => c.role === SegmentedRole.Option);
|
|
3278
|
+
}
|
|
3279
|
+
/** The key an option renders with: its own key, else its index among the options. */
|
|
3280
|
+
function segmentedOptionKey(option, index) {
|
|
3281
|
+
return option.itemKey || `item-${index}`;
|
|
3282
|
+
}
|
|
3283
|
+
/** Every option's key, in order. */
|
|
3284
|
+
function segmentedKeys(root) {
|
|
3285
|
+
return segmentedOptions(root).map((o, i) => segmentedOptionKey(o, i));
|
|
3286
|
+
}
|
|
3287
|
+
/** The words an option shows: its first Text that is not inside its badge. */
|
|
3288
|
+
function segmentedOptionLabelNodeOf(option) {
|
|
3289
|
+
for (const child of option.children ?? []) {
|
|
3290
|
+
if (child.role === SegmentedRole.Badge)
|
|
3291
|
+
continue;
|
|
3292
|
+
if (child.type === Type.Text)
|
|
3293
|
+
return child;
|
|
3294
|
+
let hit;
|
|
3295
|
+
walkComposite(child, n => { if (!hit && n.type === Type.Text)
|
|
3296
|
+
hit = n; });
|
|
3297
|
+
if (hit)
|
|
3298
|
+
return hit;
|
|
3299
|
+
}
|
|
3300
|
+
return undefined;
|
|
3301
|
+
}
|
|
3302
|
+
const stripTags = (v) => String(v ?? '').replace(/<[^>]*>/g, '').trim();
|
|
3303
|
+
/** An option's name in the Options list, the layer tree, the item pickers and "Showing on canvas": its words. */
|
|
3304
|
+
function segmentedOptionLabel(option) {
|
|
3305
|
+
const label = segmentedOptionLabelNodeOf(option);
|
|
3306
|
+
return label ? stripTags(readProp(label, 'content')) : '';
|
|
3307
|
+
}
|
|
3308
|
+
/** Options ▸ rename: the option's words. */
|
|
3309
|
+
function renameSegmentedOption(option, words) {
|
|
3310
|
+
const label = segmentedOptionLabelNodeOf(option);
|
|
3311
|
+
return !!label && writeProp(label, 'content', words);
|
|
3312
|
+
}
|
|
3313
|
+
/** The radio group's accessible name (empty when unnamed). */
|
|
3314
|
+
function segmentedLabel(root) {
|
|
3315
|
+
return root ? String(readProp(root, SegmentedTrait.Label) ?? '').trim() : '';
|
|
3316
|
+
}
|
|
3317
|
+
/** The option on by default: the stored key when it still names an option, else the first option. */
|
|
3318
|
+
function segmentedDefaultOn(root) {
|
|
3319
|
+
const keys = segmentedKeys(root);
|
|
3320
|
+
const stored = root ? String(readProp(root, SegmentedTrait.DefaultOn) ?? '') : '';
|
|
3321
|
+
return keys.includes(stored) ? stored : keys[0] ?? null;
|
|
3322
|
+
}
|
|
3323
|
+
/** Behaviour ▸ On by default and Accessibility ▸ Name. False when the root has no such trait. */
|
|
3324
|
+
function setSegmentedBehaviour(root, patch) {
|
|
3325
|
+
let ok = true;
|
|
3326
|
+
if (patch.defaultOn !== undefined)
|
|
3327
|
+
ok = writeProp(root, SegmentedTrait.DefaultOn, patch.defaultOn) && ok;
|
|
3328
|
+
if (patch.label !== undefined)
|
|
3329
|
+
ok = writeProp(root, SegmentedTrait.Label, patch.label) && ok;
|
|
3330
|
+
return ok;
|
|
3331
|
+
}
|
|
3332
|
+
// ── editing (Options list, toolbar ▸ Add option, the structure guard) ───────────────────────────
|
|
3333
|
+
var SegmentedEditRefusal;
|
|
3334
|
+
(function (SegmentedEditRefusal) {
|
|
3335
|
+
/** Q40: a Segmented control keeps at least two options. */
|
|
3336
|
+
SegmentedEditRefusal["MinOptions"] = "min-options";
|
|
3337
|
+
/** Q40: a Segmented control holds four options at most. */
|
|
3338
|
+
SegmentedEditRefusal["MaxOptions"] = "max-options";
|
|
3339
|
+
})(SegmentedEditRefusal || (SegmentedEditRefusal = {}));
|
|
3340
|
+
/** Why adding an option to `root` is refused (four already), or null. */
|
|
3341
|
+
function segmentedAddRefusal(root) {
|
|
3342
|
+
return root && segmentedOptions(root).length >= SEGMENTED_MAX_OPTIONS ? SegmentedEditRefusal.MaxOptions : null;
|
|
3343
|
+
}
|
|
3344
|
+
/** Why removing an option from `root` is refused (two left), or null. */
|
|
3345
|
+
function segmentedRemovalRefusal(root, node) {
|
|
3346
|
+
if (!root || (node && node.role !== SegmentedRole.Option))
|
|
3347
|
+
return null;
|
|
3348
|
+
return segmentedOptions(root).length <= SEGMENTED_MIN_OPTIONS ? SegmentedEditRefusal.MinOptions : null;
|
|
3349
|
+
}
|
|
3350
|
+
/** A key no option of `root` holds yet (`k3`…). */
|
|
3351
|
+
function nextSegmentedKey(root) {
|
|
3352
|
+
const taken = new Set(segmentedOptions(root).map(o => o.itemKey).filter(Boolean));
|
|
3353
|
+
let n = segmentedOptions(root).length + 1;
|
|
3354
|
+
while (taken.has(`k${n}`))
|
|
3355
|
+
n++;
|
|
3356
|
+
return `k${n}`;
|
|
3357
|
+
}
|
|
3358
|
+
/** Options ▸ Add option (and the toolbar's "Add option"): a fresh option after `after` (or at the end). Null when
|
|
3359
|
+
* refused (four already). */
|
|
3360
|
+
function addSegmentedOption(root, create, after, words = SEGMENTED_NEW_OPTION_WORDS) {
|
|
3361
|
+
if (segmentedAddRefusal(root))
|
|
3362
|
+
return null;
|
|
3363
|
+
const option = buildSegmentedOption(create, { key: nextSegmentedKey(root), label: words });
|
|
3364
|
+
const children = [...(root.children ?? [])];
|
|
3365
|
+
const at = after ? children.indexOf(after) : -1;
|
|
3366
|
+
children.splice(at < 0 ? children.length : at + 1, 0, option);
|
|
3367
|
+
root.children = children;
|
|
3368
|
+
return option;
|
|
3369
|
+
}
|
|
3370
|
+
/** Options ▸ ✕: remove an option. False when refused (two left) or `option` is not one of the root's. A removed
|
|
3371
|
+
* default hands "On by default" to the first option left. */
|
|
3372
|
+
function removeSegmentedOption(root, option) {
|
|
3373
|
+
if (segmentedRemovalRefusal(root, option) || !segmentedOptions(root).includes(option))
|
|
3374
|
+
return false;
|
|
3375
|
+
const wasDefault = segmentedDefaultOn(root) === option.itemKey;
|
|
3376
|
+
root.children = (root.children ?? []).filter(c => c !== option);
|
|
3377
|
+
if (wasDefault)
|
|
3378
|
+
writeProp(root, SegmentedTrait.DefaultOn, segmentedKeys(root)[0] ?? '');
|
|
3379
|
+
return true;
|
|
3380
|
+
}
|
|
3381
|
+
/** Options ▸ drag to reorder: move an option to `toIndex` among the options. */
|
|
3382
|
+
function moveSegmentedOption(root, option, toIndex) {
|
|
3383
|
+
const children = [...(root.children ?? [])];
|
|
3384
|
+
const from = children.indexOf(option);
|
|
3385
|
+
if (from < 0 || option.role !== SegmentedRole.Option)
|
|
3386
|
+
return false;
|
|
3387
|
+
children.splice(from, 1);
|
|
3388
|
+
const options = children.filter(c => c.role === SegmentedRole.Option);
|
|
3389
|
+
const anchor = options[Math.max(0, Math.min(toIndex, options.length))];
|
|
3390
|
+
children.splice(anchor ? children.indexOf(anchor) : children.length, 0, option);
|
|
3391
|
+
root.children = children;
|
|
3392
|
+
return true;
|
|
3393
|
+
}
|
|
3394
|
+
/** The saving badge inside an option, or undefined. */
|
|
3395
|
+
function segmentedBadgeOf(option) {
|
|
3396
|
+
return (option?.children ?? []).find(c => c.role === SegmentedRole.Badge);
|
|
3397
|
+
}
|
|
3398
|
+
/** Option ▸ Saving badge (Q33): stamp the badge (`badge-success` › Icon + Text, the placeholder words) inside the
|
|
3399
|
+
* option, after its words — or remove it. Returns the badge stamped, or null (removed / nothing changed). */
|
|
3400
|
+
function setSegmentedBadge(option, on, create) {
|
|
3401
|
+
const existing = segmentedBadgeOf(option);
|
|
3402
|
+
if (!on) {
|
|
3403
|
+
if (existing)
|
|
3404
|
+
option.children = (option.children ?? []).filter(c => c !== existing);
|
|
3405
|
+
return null;
|
|
3406
|
+
}
|
|
3407
|
+
if (existing || !create)
|
|
3408
|
+
return null;
|
|
3409
|
+
const badge = buildBadge(create, { tone: SEGMENTED_BADGE_TONE, text: SEGMENTED_BADGE_WORDS, key: `${option.itemKey ?? 'option'}-badge` });
|
|
3410
|
+
badge.role = SegmentedRole.Badge;
|
|
3411
|
+
badge.label = 'Badge';
|
|
3412
|
+
writeStyles(badge, BADGE_IN_OPTION);
|
|
3413
|
+
option.children = [...(option.children ?? []), badge];
|
|
3414
|
+
return badge;
|
|
3415
|
+
}
|
|
3416
|
+
/** Every "Option on" (`trigger-on`) state inside an option points at ITS option — the part that carries
|
|
3417
|
+
* `data-iox-on` while it is on. */
|
|
3418
|
+
function segmentedItemTriggerIds(root) {
|
|
3419
|
+
for (const option of segmentedOptions(root)) {
|
|
3420
|
+
if (!option.id)
|
|
3421
|
+
continue;
|
|
3422
|
+
walkComposite(option, n => {
|
|
3423
|
+
if (n === option || !n.stateStyles?.[ITEM_OPEN_STATE])
|
|
3424
|
+
return;
|
|
3425
|
+
n.stateTriggers = { ...(n.stateTriggers ?? {}), [ITEM_OPEN_STATE]: option.id };
|
|
3426
|
+
});
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
// ── state and attributes (both renderers, SSR included) ────────────────────────────────────────
|
|
3430
|
+
/** A Segmented control behaves like a horizontal Tabs activated by a click — only its keys and markup differ. */
|
|
3431
|
+
const SEGMENTED_BEHAVIOUR = { activateOn: TabsActivateOn.Click, orientation: TabsOrientation.Horizontal };
|
|
3432
|
+
/** The render scope of one Segmented control: its scope id, every option key in order, the one on at first paint (its
|
|
3433
|
+
* default) and its name — ONE decision for both engine paths. */
|
|
3434
|
+
function segmentedScopeFor(root, rootCssId, rowKeys = []) {
|
|
3435
|
+
const keys = segmentedKeys(root);
|
|
3436
|
+
return {
|
|
3437
|
+
kind: CompositeKind.Segmented,
|
|
3438
|
+
rootId: compositeScopeId(rootCssId, rowKeys),
|
|
3439
|
+
behaviour: SEGMENTED_BEHAVIOUR,
|
|
3440
|
+
keys,
|
|
3441
|
+
active: segmentedDefaultOn(root),
|
|
3442
|
+
label: segmentedLabel(root),
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
3445
|
+
/** The root: the radio group, named; its default and (inside repeated rows) its scope id ride on it. */
|
|
3446
|
+
function segmentedRootAttributes(label, defaultOn, scopeId) {
|
|
3447
|
+
return {
|
|
3448
|
+
[COMPOSITE_ATTRIBUTE]: CompositeKind.Segmented,
|
|
3449
|
+
role: 'radiogroup',
|
|
3450
|
+
...(label ? { 'aria-label': label } : {}),
|
|
3451
|
+
...(defaultOn ? { [DEFAULT_ON_ATTRIBUTE]: defaultOn } : {}),
|
|
3452
|
+
...(scopeId ? { [SCOPE_ATTRIBUTE]: scopeId } : {}),
|
|
3453
|
+
};
|
|
3454
|
+
}
|
|
3455
|
+
/** The root's attributes read off the node itself (the engine writes a root's own attributes from its parent's scope). */
|
|
3456
|
+
function segmentedRootAttributesOf(root, scopeId) {
|
|
3457
|
+
return segmentedRootAttributes(segmentedLabel(root), segmentedDefaultOn(root), scopeId);
|
|
3458
|
+
}
|
|
3459
|
+
/** An option: a real button announced as a radio — checked = the one option in the Tab order (roving tabindex). */
|
|
3460
|
+
function segmentedOptionAttributes(key, on) {
|
|
3461
|
+
return {
|
|
3462
|
+
type: 'button',
|
|
3463
|
+
role: 'radio',
|
|
3464
|
+
'aria-checked': on ? 'true' : 'false',
|
|
3465
|
+
tabindex: on ? '0' : '-1',
|
|
3466
|
+
[ITEM_KEY_ATTRIBUTE]: key,
|
|
3467
|
+
...(on ? { [ON_ATTRIBUTE]: '' } : {}),
|
|
3468
|
+
};
|
|
3469
|
+
}
|
|
3470
|
+
/** The attributes a part of a Segmented control writes on its own element (both engine paths, SSR included). */
|
|
3471
|
+
function segmentedPartAttributes(node, scope, key, rowScoped = false) {
|
|
3472
|
+
switch (node.role) {
|
|
3473
|
+
case SegmentedRole.Segmented: return segmentedRootAttributes(scope.label, scope.active, rowScoped ? scope.rootId : undefined);
|
|
3474
|
+
case SegmentedRole.Option: return key ? segmentedOptionAttributes(key, scope.active === key) : {};
|
|
3475
|
+
default: return {};
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
/**
|
|
3479
|
+
* The APG radio group's keys on an option: → / ↓ the next option, ← / ↑ the previous — ← and → follow the reading
|
|
3480
|
+
* direction (mirrored under RTL: ← is the next option), ↑ ↓ always the DOM order. Every move wraps and turns the option
|
|
3481
|
+
* on. Space / Enter are the button's own (a click: it turns on).
|
|
3482
|
+
*/
|
|
3483
|
+
function segmentedKeyAction(key, rtl = false) {
|
|
3484
|
+
switch (key) {
|
|
3485
|
+
case 'ArrowDown': return AccordionKeyAction.Next;
|
|
3486
|
+
case 'ArrowUp': return AccordionKeyAction.Previous;
|
|
3487
|
+
case 'ArrowRight': return rtl ? AccordionKeyAction.Previous : AccordionKeyAction.Next;
|
|
3488
|
+
case 'ArrowLeft': return rtl ? AccordionKeyAction.Next : AccordionKeyAction.Previous;
|
|
3489
|
+
default: return null;
|
|
3490
|
+
}
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
const REPEATER = 'Repeater';
|
|
3494
|
+
/** The scope an exclusive root renders its parts with: a Segmented control's options and default, or a Tabs' keys —
|
|
3495
|
+
* From content, the rows its list's Repeater resolved (`rowsOf`). `rowKeys` = the repeated rows around the root. */
|
|
3496
|
+
function exclusiveScopeFor(root, rootCssId, rowsOf, rowKeys = []) {
|
|
3497
|
+
if (isSegmentedRoot(root))
|
|
3498
|
+
return segmentedScopeFor(root, rootCssId, rowKeys);
|
|
3499
|
+
const list = tabsListOf(root);
|
|
3500
|
+
return tabsScopeFor(root, rootCssId, list?.type === REPEATER ? rowsOf(list) : null, rowKeys);
|
|
3501
|
+
}
|
|
3502
|
+
/** The attributes an exclusive ROOT writes on its own element, read off the node (its scope id when it sits in
|
|
3503
|
+
* repeated rows). */
|
|
3504
|
+
function exclusiveRootAttributesOf(root, scopeId) {
|
|
3505
|
+
return isSegmentedRoot(root) ? segmentedRootAttributesOf(root, scopeId) : tabsRootAttributes(tabsBehaviour(root), scopeId);
|
|
3506
|
+
}
|
|
3507
|
+
/** True when `node` is a part of the kind `scope` renders (a tab inside a Tabs' scope; an option inside a Segmented's). */
|
|
3508
|
+
function isPartOfExclusiveScope(node, scope) {
|
|
3509
|
+
const kind = exclusiveKindOf(node);
|
|
3510
|
+
return !!kind && kind === (scope.kind ?? CompositeKind.Tabs);
|
|
3511
|
+
}
|
|
3512
|
+
/** The attributes a part of an exclusive composite writes on its own element (both engine paths, SSR included). */
|
|
3513
|
+
function exclusivePartAttributes(node, scope, key, rowScoped = false) {
|
|
3514
|
+
return scope.kind === CompositeKind.Segmented ? segmentedPartAttributes(node, scope, key, rowScoped) : tabsPartAttributes(node, scope, key, rowScoped);
|
|
3515
|
+
}
|
|
3516
|
+
/** The behaviour an exclusive root runs with (a Segmented control: a click, a row — it has no Behaviour traits). */
|
|
3517
|
+
function exclusiveBehaviourOf(root) {
|
|
3518
|
+
return isSegmentedRoot(root) ? { ...SEGMENTED_BEHAVIOUR } : tabsBehaviour(root);
|
|
3519
|
+
}
|
|
3520
|
+
/** The accessible name an exclusive root gives its group (a Tabs' list, a Segmented control's radio group). */
|
|
3521
|
+
function exclusiveLabelOf(root) {
|
|
3522
|
+
return isSegmentedRoot(root) ? segmentedLabel(root) : tabsLabel(root);
|
|
3523
|
+
}
|
|
3524
|
+
/** The item an exclusive root shows with no other say (a Segmented control's On by default), or null (a Tabs: its first). */
|
|
3525
|
+
function exclusiveDefaultOf(root) {
|
|
3526
|
+
return isSegmentedRoot(root) ? segmentedDefaultOn(root) : null;
|
|
3527
|
+
}
|
|
3528
|
+
/** An exclusive item's words — a tab's, an option's (never its badge's) — for the pickers, the tree and the switchers. */
|
|
3529
|
+
function exclusiveItemLabel(part) {
|
|
3530
|
+
return exclusiveKindOf(part) === CompositeKind.Segmented ? segmentedOptionLabel(part) : tabLabel(part);
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3151
3533
|
const KIND_ROOT = (kind) => `[${COMPOSITE_ATTRIBUTE}="${kind}"]`;
|
|
3152
3534
|
const EXCLUSIVE_KINDS = Object.keys(EXCLUSIVE_KIND_ROLES);
|
|
3153
3535
|
const ROOT_SELECTOR = EXCLUSIVE_KINDS.map(KIND_ROOT).join(', ');
|
|
@@ -3226,7 +3608,7 @@ class ExclusiveRuntime {
|
|
|
3226
3608
|
if (!trigger)
|
|
3227
3609
|
return;
|
|
3228
3610
|
const rtl = isRtl(trigger);
|
|
3229
|
-
const action = tabsKeyAction(e.key, this.behaviourValue.orientation, rtl);
|
|
3611
|
+
const action = this.holdsTriggers ? segmentedKeyAction(e.key, rtl) : tabsKeyAction(e.key, this.behaviourValue.orientation, rtl);
|
|
3230
3612
|
if (!action)
|
|
3231
3613
|
return;
|
|
3232
3614
|
const triggers = this.triggers();
|
|
@@ -3237,14 +3619,14 @@ class ExclusiveRuntime {
|
|
|
3237
3619
|
target.focus();
|
|
3238
3620
|
this.activate(this.keyOf(target));
|
|
3239
3621
|
};
|
|
3240
|
-
this.kind = kindOf(root);
|
|
3622
|
+
this.kind = options.kind ?? kindOf(root);
|
|
3241
3623
|
this.behaviourValue = options.behaviour ?? behaviourOf(root);
|
|
3242
3624
|
root.setAttribute(COMPOSITE_ATTRIBUTE, this.kind);
|
|
3243
3625
|
EXCLUSIVE_RUNTIMES.set(root, this);
|
|
3244
|
-
// The server rendered the first state (every trigger keyed, one on); with no server state the
|
|
3626
|
+
// The server rendered the first state (every trigger keyed, one on); with no server state the default is on.
|
|
3245
3627
|
const triggers = this.triggers();
|
|
3246
3628
|
const serverOn = !options.behaviour ? triggers.find(t => t.hasAttribute(ON_ATTRIBUTE) && t.hasAttribute(ITEM_KEY_ATTRIBUTE)) : undefined;
|
|
3247
|
-
this.live = serverOn ? this.keyOf(serverOn) :
|
|
3629
|
+
this.live = serverOn ? this.keyOf(serverOn) : this.defaultKey();
|
|
3248
3630
|
this.awaitingItems = !triggers.length;
|
|
3249
3631
|
// Capture: on the builder canvas every part's own listener stops the click (it selects that part), and the tab
|
|
3250
3632
|
// must still turn on — the canvas is a live runtime.
|
|
@@ -3329,6 +3711,16 @@ class ExclusiveRuntime {
|
|
|
3329
3711
|
keys() {
|
|
3330
3712
|
return this.triggers().map(t => this.keyOf(t));
|
|
3331
3713
|
}
|
|
3714
|
+
/** The key on with no server state: the asked default when it names an item, else the first item. */
|
|
3715
|
+
defaultKey() {
|
|
3716
|
+
const keys = this.keys();
|
|
3717
|
+
const asked = this.options.defaultKey?.() ?? this.root.getAttribute(DEFAULT_ON_ATTRIBUTE);
|
|
3718
|
+
return asked && keys.includes(asked) ? asked : keys[0] ?? null;
|
|
3719
|
+
}
|
|
3720
|
+
/** True for a Segmented control: the root IS its radio group and holds its options. */
|
|
3721
|
+
get holdsTriggers() {
|
|
3722
|
+
return exclusiveRootHoldsTriggers(this.kind);
|
|
3723
|
+
}
|
|
3332
3724
|
/** The element(s) that ARE the tablist: the ones a renderer already gave the role (the engine's component-path
|
|
3333
3725
|
* Repeater puts it on its inner root, which holds the tabs), else the elements carrying the list's role class. */
|
|
3334
3726
|
lists() {
|
|
@@ -3358,13 +3750,15 @@ class ExclusiveRuntime {
|
|
|
3358
3750
|
const keys = triggers.map(t => this.keyOf(t));
|
|
3359
3751
|
if (this.awaitingItems && keys.length) {
|
|
3360
3752
|
this.awaitingItems = false;
|
|
3361
|
-
this.live =
|
|
3753
|
+
this.live = this.defaultKey();
|
|
3362
3754
|
first = true;
|
|
3363
3755
|
}
|
|
3364
3756
|
if (this.live === null || !keys.includes(this.live))
|
|
3365
|
-
this.live =
|
|
3757
|
+
this.live = this.defaultKey();
|
|
3366
3758
|
const forced = this.options.forced?.() ?? null;
|
|
3367
3759
|
const next = forced !== null && keys.includes(forced) ? forced : this.live;
|
|
3760
|
+
if (this.holdsTriggers)
|
|
3761
|
+
return this.stampRadioGroup(triggers, next, first);
|
|
3368
3762
|
const rootId = this.rootId();
|
|
3369
3763
|
const vertical = this.behaviourValue.orientation === TabsOrientation.Vertical;
|
|
3370
3764
|
for (const list of this.lists()) {
|
|
@@ -3400,6 +3794,27 @@ class ExclusiveRuntime {
|
|
|
3400
3794
|
setAttr(panel, ON_ATTRIBUTE, on ? '' : null);
|
|
3401
3795
|
setAttr(panel, 'hidden', on ? null : '');
|
|
3402
3796
|
}
|
|
3797
|
+
this.announce(triggers, next, first, panelOf);
|
|
3798
|
+
}
|
|
3799
|
+
/** A Segmented control's markup (CC4b): the root a named radio group, each option a radio, one checked. */
|
|
3800
|
+
stampRadioGroup(options, next, first) {
|
|
3801
|
+
setAttr(this.root, 'role', 'radiogroup');
|
|
3802
|
+
if (this.options.label)
|
|
3803
|
+
setAttr(this.root, 'aria-label', this.options.label() || null);
|
|
3804
|
+
for (const option of options) {
|
|
3805
|
+
const key = this.keyOf(option);
|
|
3806
|
+
const on = key === next;
|
|
3807
|
+
setAttr(option, ITEM_KEY_ATTRIBUTE, key);
|
|
3808
|
+
setAttr(option, 'type', option.tagName === 'BUTTON' ? 'button' : null);
|
|
3809
|
+
setAttr(option, 'role', 'radio');
|
|
3810
|
+
setAttr(option, 'aria-checked', on ? 'true' : 'false');
|
|
3811
|
+
setAttr(option, 'tabindex', on ? '0' : '-1');
|
|
3812
|
+
setAttr(option, ON_ATTRIBUTE, on ? '' : null);
|
|
3813
|
+
}
|
|
3814
|
+
this.announce(options, next, first);
|
|
3815
|
+
}
|
|
3816
|
+
/** Move the shown key; dispatch iox-item-off on what was on and iox-item-on on what is on now. */
|
|
3817
|
+
announce(triggers, next, first, panelOf = () => null) {
|
|
3403
3818
|
const was = first ? null : this.shown;
|
|
3404
3819
|
this.shown = next;
|
|
3405
3820
|
if (was === next && !first)
|
|
@@ -3513,8 +3928,9 @@ function runCompositeItemAction(el, action) {
|
|
|
3513
3928
|
* plan §3a). FRAMEWORK-FREE: the builder, the SSR engine and iox-cms (generation-core's composer, the site
|
|
3514
3929
|
* build) read the same recipes, roles and structure rules. Card v2 + Badge / Chip (T23 / T25) are the
|
|
3515
3930
|
* first; the Accordion (CC2) adds the first composite RUNTIME (accordion-runtime.ts — DOM only, no
|
|
3516
|
-
* framework), which the Disclosure (CC2.1) runs too — a one-item accordion whose root is its item; Tabs (CC4a)
|
|
3517
|
-
* EXCLUSIVE runtime (exclusive-runtime.ts — one item always on
|
|
3931
|
+
* framework), which the Disclosure (CC2.1) runs too — a one-item accordion whose root is its item; Tabs (CC4a) and the
|
|
3932
|
+
* Segmented control (CC4b) run the EXCLUSIVE runtime (exclusive-runtime.ts — one item always on; exclusive.ts the one
|
|
3933
|
+
* render decision by kind); composite-runtime.ts is the one door to both families.
|
|
3518
3934
|
* ⚠️ Consumers wire the usual trio — ng-package raw-ts assets, jest moduleNameMapper, tsconfig paths.
|
|
3519
3935
|
*/
|
|
3520
3936
|
|
|
@@ -3522,5 +3938,5 @@ function runCompositeItemAction(el, action) {
|
|
|
3522
3938
|
* Generated bundle index. Do not edit.
|
|
3523
3939
|
*/
|
|
3524
3940
|
|
|
3525
|
-
export { ACCORDION_BIND_PROP, ACCORDION_CONTENT_LIMIT, ACCORDION_DEFAULT_ONS, ACCORDION_ICONS, ACCORDION_ICON_GLYPH, ACCORDION_NEW_ITEM_WORDS, ACCORDION_WORDS, ACTIVATE_ON_ATTRIBUTE, AccordionClass, AccordionDefaultOn, AccordionEditRefusal, AccordionIcon, AccordionKeyAction, AccordionRole, AccordionRuntime, BADGE_TONES, BADGE_TONE_ICONS, BadgeTone, CARD_LAYOUTS, CARD_LAYOUT_CUSTOM, CARD_PARTS, CARD_PART_ROLE, CARD_STACK_BREAKPOINT, CARD_WORDS, CAROUSEL_QUOTES, CHIP_ICON, CHIP_STYLE, COMPOSITE_ATTRIBUTE, COMPOSITE_KIND_ROLES, COMPOSITE_ROLE_SPECS, CardLayout, CardPart, CardRole, CarouselA11y, CompositeBehaviourTrait, CompositeItemAction, CompositeKind, DEFAULT_ON_ATTRIBUTE, DISCLOSURE_ITEM_KEY, DISCLOSURE_WORDS, DisclosureRole, EXCLUSIVE_KIND_ROLES, ExclusiveRuntime, ITEM_KEY_ATTRIBUTE, ITEM_OFF_EVENT, ITEM_ON_EVENT, ITEM_OPEN_STATE, LIGHTBOX_ARROWS_HIDE_AT, LIGHTBOX_LABEL, MULTIPLE_ATTRIBUTE, NO_ELEMENT_STYLE, ON_ATTRIBUTE, ON_STATE, ORIENTATION_ATTRIBUTE, PILL_LABEL_STYLE, ROW_INDEX_ATTRIBUTE, SCOPE_ATTRIBUTE, SLIDER_NAME_TRAIT, SLIDER_PART_GLYPH, SLIDER_PART_ROLES, SLIDER_PLAY_GLYPH, SLIDER_TYPE, SLIDER_WORDS, SLIDE_TO_ROW, SLIDE_TYPE, SWIPER_THEME_COLOR, SliderFlag, SliderRole, StructureEdit, StructureRefusal, TABS_ACTIVATE_ONS, TABS_CONTENT_LIMIT, TABS_IMAGE_BIND_PROP, TABS_LABEL_BIND_PROP, TABS_NEW_TAB_WORDS, TABS_ORIENTATIONS, TABS_STACK_BREAKPOINT, TABS_TEXT_BIND_PROP, TABS_WORDS, TabsActivateOn, TabsBehaviourTrait, TabsEditRefusal, TabsOrientation, TabsRole, UNTIL_FOUND, acceptsRefusal, accordionAnswerOf, accordionBehaviour, accordionDomIds, accordionFocusIndex, accordionHeadingLevel, accordionItemAttributes, accordionItemLabel, accordionItemOf, accordionItemState, accordionItems, accordionKeyAction, accordionPanelAttributes, accordionPanelOf, accordionPartStyle, accordionQuestionOf, accordionRegionAttributes, accordionRemovalRefusal, accordionRootAttributes, accordionRootOf, accordionRootOfItem, accordionRuntimeOf, accordionScope, accordionToContent, accordionToManual, accordionTrackAttributes, accordionTriggerAttributes, accordionTriggerOf, addAccordionItem, addTabPair, applyCardLayout, attachAccordionAt, attachAccordions, attachCarouselA11y, attachCompositeAt, attachExclusiveAt, badgeStyleId, badgeToneOf, buildAccordionItem, buildAccordionRecipe, buildBadge, buildCardRecipe, buildCarouselRecipe, buildChip, buildCompositeIcon, buildDisclosureRecipe, buildLightboxRecipe, buildSliderPart, buildTabPair, buildTabsRecipe, canOpenAtClickedItem, cardPartsOf, cardRootOf, cardTitleOf, carouselOf, clearLayerStyles, closeAccordionItem, closestAccordion, closestAccordionItem, closestExclusive, closestExclusiveItemPart, compositeItemOf, compositeItems, compositeKindOfRole, compositeRootOf, compositeRowKeys, compositeScopeId, detachAccordion, detectAccordionIcon, detectCardLayout, disclosureOpenByDefault, domSafeKey, exclusiveItemPartOf, exclusiveRootOf, exclusiveRuntimeOf, findByRole, hasLabelledRegion, hasSliderParts, initialAccordionOpen, isAccordionFromContent, isAccordionItemOn, isAccordionPart, isAccordionRoot, isAnyCompositeRoot, isCardLinked, isCardRoot, isCompositeItem, isCompositeItemOn, isCompositePanel, isCompositeRoot, isCompositeTrigger, isDisclosurePart, isDisclosureRoot, isExclusiveItemOn, isExclusiveItemPart, isExclusiveKind, isExclusiveList, isExclusivePanel, isExclusivePanels, isExclusiveRoot, isExclusiveTrigger, isLockedPart, isPillNode, isSlider, isSliderPart, isTabsFromContent, isTabsPart, isTabsRoot, isToggleKind, itemOpenTriggerIds, lightboxSliderOf, makeCompositePart, manualTabKeys, migrateSliderControls, moveAccordionItem, moveTabPair, needsSliderMigration, nextAccordionState, nextItemKey, nextTabKey, onAccordionItemState, onCompositeItemState, onExclusiveItemState, openAccordionItem, parentInComposite, pickAccordionAutoBinds, pickTabsAutoBinds, readLayerStyle, readProp, readStyle, removeFromComposite, removeTabPair, renameAccordionItem, renameTab, repeatSourceOf, rowIndexOf, rowItemKey, rowSourceOf, runCompositeItemAction, sameRepeatSource, serializeDomAttributes, setAccordionBehaviour, setAccordionIcon, setBadgeTone, setCardLinked, setCardPart, setSliderFlag, setTabsBehaviour, shouldScrollToItem, slideIndexForRow, sliderLiveRegion, sliderPart, sliderPartElement, sliderPauseAttributes, sliderPauseGlyph, sliderSlides, sliderWords, splitSliderChildren, structureRefusal, syncSliderParts, tabLabel, tabLabelNodeOf, tabListAttributes, tabPanelAttributes, tabPanelImageOf, tabPanelOf, tabPanelTextOf, tabPartKey, tabTriggerAttributes, tabTriggerOf, tabsBehaviour, tabsItemState, tabsItemTriggerIds, tabsKeyAction, tabsLabel, tabsListOf, tabsPanels, tabsPanelsOf, tabsPartAttributes, tabsPartnerOf, tabsRemovalRefusal, tabsRootAttributes, tabsScope, tabsScopeFor, tabsToContent, tabsToManual, tabsTriggers, walkComposite, wantedSliderParts, writeLayerStyles, writeProp, writeStyles };
|
|
3941
|
+
export { ACCORDION_BIND_PROP, ACCORDION_CONTENT_LIMIT, ACCORDION_DEFAULT_ONS, ACCORDION_ICONS, ACCORDION_ICON_GLYPH, ACCORDION_NEW_ITEM_WORDS, ACCORDION_WORDS, ACTIVATE_ON_ATTRIBUTE, AccordionClass, AccordionDefaultOn, AccordionEditRefusal, AccordionIcon, AccordionKeyAction, AccordionRole, AccordionRuntime, BADGE_TONES, BADGE_TONE_ICONS, BadgeTone, CARD_LAYOUTS, CARD_LAYOUT_CUSTOM, CARD_PARTS, CARD_PART_ROLE, CARD_STACK_BREAKPOINT, CARD_WORDS, CAROUSEL_QUOTES, CHIP_ICON, CHIP_STYLE, COMPOSITE_ATTRIBUTE, COMPOSITE_KIND_ROLES, COMPOSITE_ROLE_SPECS, CardLayout, CardPart, CardRole, CarouselA11y, CompositeBehaviourTrait, CompositeItemAction, CompositeKind, DEFAULT_ON_ATTRIBUTE, DISCLOSURE_ITEM_KEY, DISCLOSURE_WORDS, DisclosureRole, EXCLUSIVE_KIND_ROLES, ExclusiveRuntime, ITEM_KEY_ATTRIBUTE, ITEM_OFF_EVENT, ITEM_ON_EVENT, ITEM_OPEN_STATE, LIGHTBOX_ARROWS_HIDE_AT, LIGHTBOX_LABEL, MULTIPLE_ATTRIBUTE, NO_ELEMENT_STYLE, ON_ATTRIBUTE, ON_STATE, ORIENTATION_ATTRIBUTE, PILL_LABEL_STYLE, ROW_INDEX_ATTRIBUTE, SCOPE_ATTRIBUTE, SEGMENTED_BADGE_TONE, SEGMENTED_BADGE_WORDS, SEGMENTED_BEHAVIOUR, SEGMENTED_MAX_OPTIONS, SEGMENTED_MIN_OPTIONS, SEGMENTED_NAME, SEGMENTED_NEW_OPTION_WORDS, SEGMENTED_WORDS, SLIDER_NAME_TRAIT, SLIDER_PART_GLYPH, SLIDER_PART_ROLES, SLIDER_PLAY_GLYPH, SLIDER_TYPE, SLIDER_WORDS, SLIDE_TO_ROW, SLIDE_TYPE, SWIPER_THEME_COLOR, SegmentedEditRefusal, SegmentedRole, SegmentedTrait, SliderFlag, SliderRole, StructureEdit, StructureRefusal, TABS_ACTIVATE_ONS, TABS_CONTENT_LIMIT, TABS_IMAGE_BIND_PROP, TABS_LABEL_BIND_PROP, TABS_NEW_TAB_WORDS, TABS_ORIENTATIONS, TABS_STACK_BREAKPOINT, TABS_TEXT_BIND_PROP, TABS_WORDS, TabsActivateOn, TabsBehaviourTrait, TabsEditRefusal, TabsOrientation, TabsRole, UNTIL_FOUND, acceptsRefusal, accordionAnswerOf, accordionBehaviour, accordionDomIds, accordionFocusIndex, accordionHeadingLevel, accordionItemAttributes, accordionItemLabel, accordionItemOf, accordionItemState, accordionItems, accordionKeyAction, accordionPanelAttributes, accordionPanelOf, accordionPartStyle, accordionQuestionOf, accordionRegionAttributes, accordionRemovalRefusal, accordionRootAttributes, accordionRootOf, accordionRootOfItem, accordionRuntimeOf, accordionScope, accordionToContent, accordionToManual, accordionTrackAttributes, accordionTriggerAttributes, accordionTriggerOf, addAccordionItem, addSegmentedOption, addTabPair, applyCardLayout, attachAccordionAt, attachAccordions, attachCarouselA11y, attachCompositeAt, attachExclusiveAt, badgeStyleId, badgeToneOf, buildAccordionItem, buildAccordionRecipe, buildBadge, buildCardRecipe, buildCarouselRecipe, buildChip, buildCompositeIcon, buildDisclosureRecipe, buildLightboxRecipe, buildSegmentedOption, buildSegmentedRecipe, buildSliderPart, buildTabPair, buildTabsRecipe, canOpenAtClickedItem, cardPartsOf, cardRootOf, cardTitleOf, carouselOf, clearLayerStyles, closeAccordionItem, closestAccordion, closestAccordionItem, closestExclusive, closestExclusiveItemPart, compositeItemOf, compositeItems, compositeKindOfRole, compositeRootOf, compositeRowKeys, compositeScopeId, detachAccordion, detectAccordionIcon, detectCardLayout, disclosureOpenByDefault, domSafeKey, exclusiveBehaviourOf, exclusiveDefaultOf, exclusiveItemLabel, exclusiveItemPartOf, exclusiveKindHasPanels, exclusiveKindOf, exclusiveLabelOf, exclusivePartAttributes, exclusiveRootAttributesOf, exclusiveRootHoldsTriggers, exclusiveRootOf, exclusiveRuntimeOf, exclusiveScopeFor, findByRole, hasLabelledRegion, hasSliderParts, initialAccordionOpen, isAccordionFromContent, isAccordionItemOn, isAccordionPart, isAccordionRoot, isAnyCompositeRoot, isCardLinked, isCardRoot, isCompositeItem, isCompositeItemOn, isCompositePanel, isCompositeRoot, isCompositeTrigger, isDisclosurePart, isDisclosureRoot, isExclusiveItemOn, isExclusiveItemPart, isExclusiveKind, isExclusiveList, isExclusivePanel, isExclusivePanels, isExclusiveRoot, isExclusiveTrigger, isLockedPart, isPartOfExclusiveScope, isPillNode, isSegmentedPart, isSegmentedRoot, isSlider, isSliderPart, isTabsFromContent, isTabsPart, isTabsRoot, isToggleKind, itemOpenTriggerIds, lightboxSliderOf, makeCompositePart, manualTabKeys, migrateSliderControls, moveAccordionItem, moveSegmentedOption, moveTabPair, needsSliderMigration, nextAccordionState, nextItemKey, nextSegmentedKey, nextTabKey, onAccordionItemState, onCompositeItemState, onExclusiveItemState, openAccordionItem, parentInComposite, pickAccordionAutoBinds, pickTabsAutoBinds, readLayerStyle, readProp, readStyle, removeFromComposite, removeSegmentedOption, removeTabPair, renameAccordionItem, renameSegmentedOption, renameTab, repeatSourceOf, rowIndexOf, rowItemKey, rowSourceOf, runCompositeItemAction, sameRepeatSource, segmentedAddRefusal, segmentedBadgeOf, segmentedDefaultOn, segmentedItemTriggerIds, segmentedKeyAction, segmentedKeys, segmentedLabel, segmentedOptionAttributes, segmentedOptionKey, segmentedOptionLabel, segmentedOptionLabelNodeOf, segmentedOptions, segmentedPartAttributes, segmentedRemovalRefusal, segmentedRootAttributes, segmentedRootAttributesOf, segmentedScopeFor, serializeDomAttributes, setAccordionBehaviour, setAccordionIcon, setBadgeTone, setCardLinked, setCardPart, setSegmentedBadge, setSegmentedBehaviour, setSliderFlag, setTabsBehaviour, shouldScrollToItem, slideIndexForRow, sliderLiveRegion, sliderPart, sliderPartElement, sliderPauseAttributes, sliderPauseGlyph, sliderSlides, sliderWords, splitSliderChildren, structureRefusal, syncSliderParts, tabLabel, tabLabelNodeOf, tabListAttributes, tabPanelAttributes, tabPanelImageOf, tabPanelOf, tabPanelTextOf, tabPartKey, tabTriggerAttributes, tabTriggerOf, tabsBehaviour, tabsItemState, tabsItemTriggerIds, tabsKeyAction, tabsLabel, tabsListOf, tabsPanels, tabsPanelsOf, tabsPartAttributes, tabsPartnerOf, tabsRemovalRefusal, tabsRootAttributes, tabsScope, tabsScopeFor, tabsToContent, tabsToManual, tabsTriggers, walkComposite, wantedSliderParts, writeLayerStyles, writeProp, writeStyles };
|
|
3526
3942
|
//# sourceMappingURL=vectoriox-iox-ui-composite-core.mjs.map
|