@vectoriox/iox-ui 5.36.0 → 5.37.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.
@@ -1,5 +1,17 @@
1
1
  import { readOwnedTrait, writeOwnedTrait, elementStyleById, switchElementStyle, LinkType, setColorScheme, COLOR_SCHEME_TRAIT } from '@vectoriox/iox-ui/design-core';
2
2
 
3
+ /** A recipe part: a NEW node (`create`) playing `role` under `label`, with the layout `base` styles its recipe
4
+ * gives it — its LOOK is its element style. Shared by every recipe (Accordion, Disclosure). */
5
+ function makeCompositePart(create, spec, role, label, base) {
6
+ const node = create(spec);
7
+ if (role)
8
+ node.role = role;
9
+ if (label)
10
+ node.label = label;
11
+ if (base)
12
+ writeStyles(node, base);
13
+ return node;
14
+ }
3
15
  const usesTraits = (node) => Array.isArray(node.traits);
4
16
  /** A content trait's value — `traits[].default` on a builder node, `props[name]` on a saved one. */
5
17
  function readProp(node, name) {
@@ -105,6 +117,101 @@ function removeFromComposite(root, node) {
105
117
  return true;
106
118
  }
107
119
 
120
+ /**
121
+ * The open/close composites' kinds and roles (CC2 Accordion, CC2.1 Disclosure — composite plan T9 / T21). ONE
122
+ * family: a Disclosure is a one-item accordion whose ROOT is its item, so both run the same state machine, the
123
+ * same attribute planners and the same runtime (`accordion-runtime.ts`). Every consumer that treats "an item",
124
+ * "a header" or "a panel" alike asks these predicates, never a role literal.
125
+ */
126
+ var CompositeKind;
127
+ (function (CompositeKind) {
128
+ CompositeKind["Accordion"] = "accordion";
129
+ CompositeKind["Disclosure"] = "disclosure";
130
+ })(CompositeKind || (CompositeKind = {}));
131
+ var AccordionRole;
132
+ (function (AccordionRole) {
133
+ AccordionRole["Accordion"] = "accordion";
134
+ AccordionRole["Item"] = "accordion.item";
135
+ AccordionRole["Trigger"] = "accordion.trigger";
136
+ AccordionRole["Icon"] = "accordion.icon";
137
+ AccordionRole["Panel"] = "accordion.panel";
138
+ })(AccordionRole || (AccordionRole = {}));
139
+ /**
140
+ * DISCLOSURE ("Read more") — one trigger, one panel. The root IS its item (it carries the item key and
141
+ * `data-iox-on`); the trigger holds two words, the one shown while closed (`more`) and the one shown while open
142
+ * (`less`), swapped by the Item open state — never by script.
143
+ */
144
+ var DisclosureRole;
145
+ (function (DisclosureRole) {
146
+ DisclosureRole["Disclosure"] = "disclosure";
147
+ DisclosureRole["Trigger"] = "disclosure.trigger";
148
+ DisclosureRole["More"] = "disclosure.more";
149
+ DisclosureRole["Less"] = "disclosure.less";
150
+ DisclosureRole["Icon"] = "disclosure.icon";
151
+ DisclosureRole["Panel"] = "disclosure.panel";
152
+ })(DisclosureRole || (DisclosureRole = {}));
153
+ const COMPOSITE_KIND_ROLES = {
154
+ [CompositeKind.Accordion]: { root: AccordionRole.Accordion, item: AccordionRole.Item, trigger: AccordionRole.Trigger, panel: AccordionRole.Panel, icon: AccordionRole.Icon },
155
+ [CompositeKind.Disclosure]: { root: DisclosureRole.Disclosure, item: DisclosureRole.Disclosure, trigger: DisclosureRole.Trigger, panel: DisclosureRole.Panel, icon: DisclosureRole.Icon },
156
+ };
157
+ const KINDS = Object.values(CompositeKind);
158
+ const rolesOf = (pick) => new Set(KINDS.map(k => COMPOSITE_KIND_ROLES[k][pick]));
159
+ const ROOT_ROLES = rolesOf('root');
160
+ const ITEM_ROLES = rolesOf('item');
161
+ const TRIGGER_ROLES = rolesOf('trigger');
162
+ const PANEL_ROLES = rolesOf('panel');
163
+ /** The kind a role belongs to (`disclosure.panel` → Disclosure), or null for any other role. */
164
+ function compositeKindOfRole(role) {
165
+ const head = role?.split('.')[0];
166
+ return KINDS.includes(head ?? '') ? head : null;
167
+ }
168
+ /** A root that runs the open/close runtime (an Accordion or a Disclosure). */
169
+ function isCompositeRoot(node) {
170
+ return !!node?.role && ROOT_ROLES.has(node.role);
171
+ }
172
+ /** An ITEM — an accordion's item, or a disclosure (its own item). */
173
+ function isCompositeItem(node) {
174
+ return !!node?.role && ITEM_ROLES.has(node.role);
175
+ }
176
+ /** A trigger — rendered as a `<button>` that opens its item, never a link. */
177
+ function isCompositeTrigger(node) {
178
+ return !!node?.role && TRIGGER_ROLES.has(node.role);
179
+ }
180
+ /** A panel — rendered inside the track + region the one hiding rule collapses. */
181
+ function isCompositePanel(node) {
182
+ return !!node?.role && PANEL_ROLES.has(node.role);
183
+ }
184
+ function isDisclosureRoot(node) {
185
+ return node?.role === DisclosureRole.Disclosure;
186
+ }
187
+ /** True for any part of a disclosure (its root included). */
188
+ function isDisclosurePart(node) {
189
+ return compositeKindOfRole(node?.role) === CompositeKind.Disclosure;
190
+ }
191
+ /** The composite root a node belongs to: the nearest ancestor (or the node itself) that is one. `path` = the
192
+ * node's ancestors, root first. */
193
+ function compositeRootOf(node, path) {
194
+ if (isCompositeRoot(node))
195
+ return node;
196
+ for (let i = path.length - 1; i >= 0; i--)
197
+ if (isCompositeRoot(path[i]))
198
+ return path[i];
199
+ return null;
200
+ }
201
+ /** The item a node sits in — the nearest item of either kind (itself when it is one) — or null. An accordion
202
+ * root ends the walk (its items are below it). */
203
+ function compositeItemOf(node, path) {
204
+ if (isCompositeItem(node))
205
+ return node;
206
+ for (let i = path.length - 1; i >= 0; i--) {
207
+ if (isCompositeItem(path[i]))
208
+ return path[i];
209
+ if (isCompositeRoot(path[i]))
210
+ return null;
211
+ }
212
+ return null;
213
+ }
214
+
108
215
  /**
109
216
  * ACCORDION (CC2 — composite K1; complex-components boards 3, 4 and 9, APPROVED 2026-10-20). Not a
110
217
  * component: a recipe of ordinary nodes, each part carrying a role, so every part is selectable on the
@@ -123,14 +230,6 @@ function removeFromComposite(root, node) {
123
230
  * (`nextAccordionState`), the ARIA and DOM state one planner (`accordionItemState` and the attribute
124
231
  * functions), the keyboard one key map — read by the builder canvas, both engine paths and the composer.
125
232
  */
126
- var AccordionRole;
127
- (function (AccordionRole) {
128
- AccordionRole["Accordion"] = "accordion";
129
- AccordionRole["Item"] = "accordion.item";
130
- AccordionRole["Trigger"] = "accordion.trigger";
131
- AccordionRole["Icon"] = "accordion.icon";
132
- AccordionRole["Panel"] = "accordion.panel";
133
- })(AccordionRole || (AccordionRole = {}));
134
233
  /** The behaviour options a composite root stores as ordinary TRAITS (composite plan §3). */
135
234
  var CompositeBehaviourTrait;
136
235
  (function (CompositeBehaviourTrait) {
@@ -172,7 +271,7 @@ const ACCORDION_WORDS = [
172
271
  { question: 'Write the third question', answer: 'Answer it plainly, in two or three short sentences.' },
173
272
  ];
174
273
  const ACCORDION_NEW_ITEM_WORDS = { question: 'Write a new question', answer: 'Answer it plainly, in two or three short sentences.' };
175
- var Type$1;
274
+ var Type$2;
176
275
  (function (Type) {
177
276
  Type["Container"] = "Container";
178
277
  Type["LinkedContainer"] = "LinkedContainer";
@@ -180,8 +279,8 @@ var Type$1;
180
279
  Type["Text"] = "Text";
181
280
  Type["Icon"] = "Icon";
182
281
  Type["Repeater"] = "Repeater";
183
- })(Type$1 || (Type$1 = {}));
184
- var Style$1;
282
+ })(Type$2 || (Type$2 = {}));
283
+ var Style$2;
185
284
  (function (Style) {
186
285
  Style["Root"] = "box";
187
286
  Style["Item"] = "accordion-item";
@@ -190,14 +289,14 @@ var Style$1;
190
289
  Style["Icon"] = "icon";
191
290
  Style["Panel"] = "accordion-panel";
192
291
  Style["Answer"] = "body";
193
- })(Style$1 || (Style$1 = {}));
194
- const ZERO_BOX$1 = '0px 0px 0px 0px';
292
+ })(Style$2 || (Style$2 = {}));
293
+ const ZERO_BOX$2 = '0px 0px 0px 0px';
195
294
  const HEADING_LEVELS = ['h2', 'h3', 'h4', 'h5', 'h6'];
196
295
  const DEFAULT_LEVEL = 'h3';
197
296
  /** A linked trait the header must never carry: it is a button, not a link. */
198
297
  const LINK_TYPE$1 = 'linkType';
199
298
  /** What each part holds in the recipe — layout only; its LOOK is its element style. */
200
- const PART_BASE = {
299
+ const PART_BASE$1 = {
201
300
  // The items are a column a token apart (`accordion-gap`: 0 for a divider list, 12px for cards).
202
301
  [AccordionRole.Accordion]: { display: 'flex', flexDirection: 'column', gap: 'var(--iox-accordion-gap)', width: '100%', minHeight: 'auto', height: 'auto' },
203
302
  [AccordionRole.Item]: { display: 'block', width: '100%', minHeight: 'auto', height: 'auto' },
@@ -208,22 +307,16 @@ const PART_BASE = {
208
307
  };
209
308
  /** The question INHERITS the header's ink (board 4's wears table: the Question reads no colour token), so the
210
309
  * header's hover ink reaches the words; and it sits in the row with no margin of its own. */
211
- const QUESTION_BASE = { color: 'inherit', margin: ZERO_BOX$1, flexGrow: '1', minWidth: '0px' };
212
- const ANSWER_BASE = { margin: ZERO_BOX$1, padding: ZERO_BOX$1, minHeight: 'auto' };
213
- function make$1(create, key, type, role, style, props = {}, label) {
214
- const node = create({ key, type, props, elementStyle: style });
215
- if (role)
216
- node.role = role;
217
- if (label)
218
- node.label = label;
219
- if (role && PART_BASE[role])
220
- writeStyles(node, PART_BASE[role]);
221
- return node;
222
- }
223
- /** The item's icon; its Item-open state (the turn) is driven by ITS item (`stateTriggers`). */
224
- function buildIcon(create, key, icon, itemId) {
310
+ const QUESTION_BASE = { color: 'inherit', margin: ZERO_BOX$2, flexGrow: '1', minWidth: '0px' };
311
+ const ANSWER_BASE = { margin: ZERO_BOX$2, padding: ZERO_BOX$2, minHeight: 'auto' };
312
+ function make$2(create, key, type, role, style, props = {}, label) {
313
+ return makeCompositePart(create, { key, type, props, elementStyle: style }, role, label, role ? PART_BASE$1[role] : undefined);
314
+ }
315
+ /** The icon of an item (an accordion's, or a disclosure's — `role`); its Item-open state (the turn) is driven by
316
+ * ITS item (`stateTriggers`). ONE builder for both kinds. */
317
+ function buildCompositeIcon(create, key, icon, itemId, role = AccordionRole.Icon) {
225
318
  const glyph = ACCORDION_ICON_GLYPH[icon];
226
- const node = make$1(create, key, Type$1.Icon, AccordionRole.Icon, Style$1.Icon, { iconClass: glyph.iconClass }, 'Icon');
319
+ const node = makeCompositePart(create, { key, type: Type$2.Icon, props: { iconClass: glyph.iconClass }, elementStyle: Style$2.Icon }, role, 'Icon', PART_BASE$1[AccordionRole.Icon]);
227
320
  node.stateStyles = { ...(node.stateStyles ?? {}), [ITEM_OPEN_STATE]: { rotate: glyph.turn } };
228
321
  if (itemId)
229
322
  node.stateTriggers = { ...(node.stateTriggers ?? {}), [ITEM_OPEN_STATE]: itemId };
@@ -231,14 +324,14 @@ function buildIcon(create, key, icon, itemId) {
231
324
  }
232
325
  /** One item: header (question + icon) and panel (answer). `key` makes every part's recipe key unique. */
233
326
  function buildAccordionItem(create, words, itemKey, icon = AccordionIcon.Chevron) {
234
- const item = make$1(create, `${itemKey}`, Type$1.Container, AccordionRole.Item, Style$1.Item, {}, 'Item');
327
+ const item = make$2(create, `${itemKey}`, Type$2.Container, AccordionRole.Item, Style$2.Item, {}, 'Item');
235
328
  item.itemKey = itemKey;
236
- const trigger = make$1(create, `${itemKey}-trigger`, Type$1.LinkedContainer, AccordionRole.Trigger, Style$1.Trigger, {}, 'Header');
237
- const question = make$1(create, `${itemKey}-question`, Type$1.Heading, null, Style$1.Question, { content: words.question, level: DEFAULT_LEVEL }, 'Heading');
329
+ const trigger = make$2(create, `${itemKey}-trigger`, Type$2.LinkedContainer, AccordionRole.Trigger, Style$2.Trigger, {}, 'Header');
330
+ const question = make$2(create, `${itemKey}-question`, Type$2.Heading, null, Style$2.Question, { content: words.question, level: DEFAULT_LEVEL }, 'Heading');
238
331
  writeStyles(question, QUESTION_BASE);
239
- trigger.children = [question, ...(icon === AccordionIcon.None ? [] : [buildIcon(create, `${itemKey}-icon`, icon, item.id)])];
240
- const panel = make$1(create, `${itemKey}-panel`, Type$1.Container, AccordionRole.Panel, Style$1.Panel, {}, 'Panel');
241
- const answer = make$1(create, `${itemKey}-answer`, Type$1.Text, null, Style$1.Answer, { content: words.answer, tag: 'p' }, 'Text');
332
+ trigger.children = [question, ...(icon === AccordionIcon.None ? [] : [buildCompositeIcon(create, `${itemKey}-icon`, icon, item.id)])];
333
+ const panel = make$2(create, `${itemKey}-panel`, Type$2.Container, AccordionRole.Panel, Style$2.Panel, {}, 'Panel');
334
+ const answer = make$2(create, `${itemKey}-answer`, Type$2.Text, null, Style$2.Answer, { content: words.answer, tag: 'p' }, 'Text');
242
335
  writeStyles(answer, ANSWER_BASE);
243
336
  panel.children = [answer];
244
337
  item.children = [trigger, panel];
@@ -246,7 +339,7 @@ function buildAccordionItem(create, words, itemKey, icon = AccordionIcon.Chevron
246
339
  }
247
340
  /** A fresh Accordion: three items, one open at a time, the first open by default (board 4). */
248
341
  function buildAccordionRecipe(create, options = {}) {
249
- const root = make$1(create, 'accordion', Type$1.Container, AccordionRole.Accordion, Style$1.Root, {}, 'Accordion');
342
+ const root = make$2(create, 'accordion', Type$2.Container, AccordionRole.Accordion, Style$2.Root, {}, 'Accordion');
250
343
  writeProp(root, CompositeBehaviourTrait.Multiple, options.multiple ?? false);
251
344
  writeProp(root, CompositeBehaviourTrait.DefaultOn, options.defaultOn ?? AccordionDefaultOn.First);
252
345
  const words = options.items ?? ACCORDION_WORDS;
@@ -285,7 +378,7 @@ function accordionItemOf(node, path) {
285
378
  }
286
379
  /** True when the root takes its items from content (a Repeater bound to a list, R3). */
287
380
  function isAccordionFromContent(root) {
288
- return !!root && root.type === Type$1.Repeater;
381
+ return !!root && root.type === Type$2.Repeater;
289
382
  }
290
383
  /** The root's items in order — every item (Manual), or the one template item (From content). */
291
384
  function accordionItems(root) {
@@ -307,29 +400,43 @@ function setAccordionBehaviour(root, patch) {
307
400
  ok = writeProp(root, CompositeBehaviourTrait.DefaultOn, patch.defaultOn) && ok;
308
401
  return ok;
309
402
  }
310
- /** The header of an item. */
403
+ /** The roles of the kind an item belongs to (an accordion item, or a disclosure — its own item). */
404
+ function kindRolesOf(node) {
405
+ return COMPOSITE_KIND_ROLES[compositeKindOfRole(node.role) ?? CompositeKind.Accordion];
406
+ }
407
+ /** The header of an item (either kind). */
311
408
  function accordionTriggerOf(item) {
312
- return findByRole(item, AccordionRole.Trigger);
409
+ return findByRole(item, kindRolesOf(item).trigger);
313
410
  }
314
- /** The panel of an item. */
411
+ /** The panel of an item (either kind). */
315
412
  function accordionPanelOf(item) {
316
- return findByRole(item, AccordionRole.Panel);
413
+ return findByRole(item, kindRolesOf(item).panel);
317
414
  }
318
- /** The question: the first Heading inside the header. */
415
+ /** A root's items — an accordion's (`accordionItems`), or a disclosure, which is its own one item. */
416
+ function compositeItems(root) {
417
+ return compositeKindOfRole(root.role) === CompositeKind.Disclosure ? [root] : accordionItems(root);
418
+ }
419
+ /** The question: the first Heading inside the header — a disclosure's toggle holds no heading, so its first word
420
+ * (the Text shown while closed, "Read more"). */
319
421
  function accordionQuestionOf(item) {
320
422
  const trigger = accordionTriggerOf(item);
321
423
  let hit;
424
+ let text;
322
425
  if (trigger)
323
- walkComposite(trigger, n => { if (!hit && n.type === Type$1.Heading)
324
- hit = n; });
325
- return hit;
426
+ walkComposite(trigger, n => {
427
+ if (!hit && n.type === Type$2.Heading)
428
+ hit = n;
429
+ if (!text && n.type === Type$2.Text)
430
+ text = n;
431
+ });
432
+ return hit ?? text;
326
433
  }
327
434
  /** The answer: the first Text inside the panel. */
328
435
  function accordionAnswerOf(item) {
329
436
  const panel = accordionPanelOf(item);
330
437
  let hit;
331
438
  if (panel)
332
- walkComposite(panel, n => { if (!hit && n.type === Type$1.Text)
439
+ walkComposite(panel, n => { if (!hit && n.type === Type$2.Text)
333
440
  hit = n; });
334
441
  return hit;
335
442
  }
@@ -350,7 +457,7 @@ function accordionHeadingLevel(trigger) {
350
457
  const visit = (n) => {
351
458
  if (level !== undefined)
352
459
  return;
353
- if (n.type === Type$1.Heading) {
460
+ if (n.type === Type$2.Heading) {
354
461
  level = readProp(n, 'level');
355
462
  return;
356
463
  }
@@ -406,10 +513,10 @@ function accordionRemovalRefusal(root, item) {
406
513
  return null;
407
514
  return accordionItems(root).length <= 1 ? AccordionEditRefusal.LastItem : null;
408
515
  }
409
- /** The icon the accordion's items wear now (the first item's; None when it has no icon). */
516
+ /** The icon the composite's items wear now (the first item's; None when it has no icon). Either kind. */
410
517
  function detectAccordionIcon(root) {
411
- const first = accordionItems(root)[0];
412
- const icon = first ? findByRole(first, AccordionRole.Icon) : undefined;
518
+ const first = compositeItems(root)[0];
519
+ const icon = first ? findByRole(first, kindRolesOf(root).icon) : undefined;
413
520
  if (!icon)
414
521
  return first ? AccordionIcon.None : AccordionIcon.Chevron;
415
522
  const cls = String(readProp(icon, 'iconClass') ?? '');
@@ -422,11 +529,12 @@ function detectAccordionIcon(root) {
422
529
  */
423
530
  function setAccordionIcon(root, icon, create) {
424
531
  let changed = 0;
425
- for (const item of accordionItems(root)) {
532
+ const iconRole = kindRolesOf(root).icon;
533
+ for (const item of compositeItems(root)) {
426
534
  const trigger = accordionTriggerOf(item);
427
535
  if (!trigger)
428
536
  continue;
429
- const existing = findByRole(trigger, AccordionRole.Icon);
537
+ const existing = findByRole(trigger, iconRole);
430
538
  if (icon === AccordionIcon.None) {
431
539
  if (existing && removeFromComposite(trigger, existing))
432
540
  changed++;
@@ -439,7 +547,7 @@ function setAccordionIcon(root, icon, create) {
439
547
  changed++;
440
548
  }
441
549
  else if (create) {
442
- trigger.children = [...(trigger.children ?? []), buildIcon(create, `${item.itemKey ?? 'item'}-icon`, icon, item.id)];
550
+ trigger.children = [...(trigger.children ?? []), buildCompositeIcon(create, `${item.itemKey ?? 'item'}-icon`, icon, item.id, iconRole)];
443
551
  changed++;
444
552
  }
445
553
  }
@@ -475,10 +583,16 @@ function nextAccordionState(open, key, behaviour) {
475
583
  }
476
584
  return behaviour.multiple ? new Set([...open, key]) : new Set([key]);
477
585
  }
478
- /** The open keys after OPENING `key` (find-in-page, selecting into a closed panel). */
586
+ /** The open keys after OPENING `key` (find-in-page, selecting into a closed panel, the `openItem` action). */
479
587
  function openAccordionItem(open, key, behaviour) {
480
588
  return open.has(key) ? new Set(open) : nextAccordionState(open, key, behaviour);
481
589
  }
590
+ /** The open keys after CLOSING `key` (the `closeItem` action). */
591
+ function closeAccordionItem(open, key) {
592
+ const next = new Set(open);
593
+ next.delete(key);
594
+ return next;
595
+ }
482
596
  var AccordionKeyAction;
483
597
  (function (AccordionKeyAction) {
484
598
  AccordionKeyAction["Next"] = "next";
@@ -520,17 +634,30 @@ function rowItemKey(row, index) {
520
634
  const id = r && typeof r === 'object' ? (r._id ?? r.id) : undefined;
521
635
  return id !== undefined && id !== null && id !== '' ? domSafeKey(id) : `row-${index}`;
522
636
  }
523
- /** The ARIA pairing ids of an item: unique per accordion (its root's node id) and per item (its key). */
524
- function accordionDomIds(rootId, itemKey) {
525
- const base = `iox-acc-${domSafeKey(rootId)}-${domSafeKey(itemKey)}`;
637
+ const DOM_ID_PREFIX = {
638
+ [CompositeKind.Accordion]: 'iox-acc',
639
+ [CompositeKind.Disclosure]: 'iox-dis',
640
+ };
641
+ /** The ARIA pairing ids of an item: unique per composite (its SCOPE id — `compositeScopeId`) and per item (its key). */
642
+ function accordionDomIds(rootId, itemKey, kind = CompositeKind.Accordion) {
643
+ const base = `${DOM_ID_PREFIX[kind]}-${domSafeKey(rootId)}-${domSafeKey(itemKey)}`;
526
644
  return { trigger: `${base}-h`, panel: `${base}-p` };
527
645
  }
528
- function accordionScope(rootId, behaviour, keys) {
529
- return { rootId, behaviour, keys, open: initialAccordionOpen(keys, behaviour.defaultOn) };
646
+ /**
647
+ * The id a rendered composite's ARIA ids are built from: its node id, and — when it sits inside repeated rows —
648
+ * every enclosing row's identity, outermost first (vectoriox/iox-ui#20: an accordion repeated per card used to
649
+ * pair every row's header with the FIRST row's panel). The engine folds each row's `rowItemKey`; the builder
650
+ * canvas its preview row's index. Unrepeated, it is the node id unchanged.
651
+ */
652
+ function compositeScopeId(rootId, rowKeys = []) {
653
+ return rowKeys.length ? [rootId, ...rowKeys.map(domSafeKey)].join('-') : rootId;
654
+ }
655
+ function accordionScope(rootId, behaviour, keys, kind = CompositeKind.Accordion) {
656
+ return { kind, rootId, behaviour, keys, open: initialAccordionOpen(keys, behaviour.defaultOn) };
530
657
  }
531
658
  function accordionItemState(scope, index) {
532
659
  const key = scope.keys[index] ?? `item-${index}`;
533
- return { key, index, on: scope.open.has(key), ids: accordionDomIds(scope.rootId, key) };
660
+ return { kind: scope.kind, key, index, on: scope.open.has(key), ids: accordionDomIds(scope.rootId, key, scope.kind) };
534
661
  }
535
662
  /** `data-iox-on` — the ONE attribute every "on" style keys on (Open, Item open). */
536
663
  const ON_ATTRIBUTE = 'data-iox-on';
@@ -538,6 +665,9 @@ const ITEM_KEY_ATTRIBUTE = 'data-iox-item-key';
538
665
  const COMPOSITE_ATTRIBUTE = 'data-iox-composite';
539
666
  const MULTIPLE_ATTRIBUTE = 'data-iox-multiple';
540
667
  const DEFAULT_ON_ATTRIBUTE = 'data-iox-default-on';
668
+ /** The scope id the ARIA ids are built from (`compositeScopeId`), written by the engine so the runtime re-stamps
669
+ * exactly the ids the server rendered. */
670
+ const SCOPE_ATTRIBUTE = 'data-iox-scope';
541
671
  /** The value a closed panel's region carries: find-in-page can still reveal it (D9, board 4). */
542
672
  const UNTIL_FOUND = 'until-found';
543
673
  /** The structural classes the renderers write around an accordion's parts (no node owns them). */
@@ -548,11 +678,12 @@ var AccordionClass;
548
678
  AccordionClass["Track"] = "iox-accordion-track";
549
679
  AccordionClass["Region"] = "iox-accordion-region";
550
680
  })(AccordionClass || (AccordionClass = {}));
551
- function accordionRootAttributes(behaviour) {
681
+ function accordionRootAttributes(behaviour, kind = CompositeKind.Accordion, scopeId) {
552
682
  return {
553
- [COMPOSITE_ATTRIBUTE]: AccordionRole.Accordion,
683
+ [COMPOSITE_ATTRIBUTE]: kind,
554
684
  [DEFAULT_ON_ATTRIBUTE]: behaviour.defaultOn,
555
- ...(behaviour.multiple ? { [MULTIPLE_ATTRIBUTE]: '' } : {}),
685
+ ...(behaviour.multiple && kind === CompositeKind.Accordion ? { [MULTIPLE_ATTRIBUTE]: '' } : {}),
686
+ ...(scopeId ? { [SCOPE_ATTRIBUTE]: scopeId } : {}),
556
687
  };
557
688
  }
558
689
  function accordionItemAttributes(state) {
@@ -571,15 +702,21 @@ function accordionTriggerAttributes(state) {
571
702
  function accordionTrackAttributes(state) {
572
703
  return state.on ? { [ON_ATTRIBUTE]: '' } : {};
573
704
  }
574
- /** The region the panel sits in: `hidden="until-found"` while closed, so its words stay in the page. */
705
+ /** The region the panel sits in: `hidden="until-found"` while closed, so its words stay in the page. An accordion's
706
+ * is a labelled `region` (APG accordion); a disclosure's is a plain block (APG disclosure: the button names it). */
575
707
  function accordionRegionAttributes(state) {
708
+ const labelled = hasLabelledRegion(state.kind);
576
709
  return {
577
- role: 'region',
710
+ ...(labelled ? { role: 'region' } : {}),
578
711
  id: state.ids.panel,
579
- 'aria-labelledby': state.ids.trigger,
712
+ ...(labelled ? { 'aria-labelledby': state.ids.trigger } : {}),
580
713
  ...(state.on ? {} : { hidden: UNTIL_FOUND }),
581
714
  };
582
715
  }
716
+ /** True when a region of this kind is a labelled `role=region`. */
717
+ function hasLabelledRegion(kind) {
718
+ return kind === CompositeKind.Accordion;
719
+ }
583
720
  function accordionPanelAttributes(state) {
584
721
  return state.on ? { [ON_ATTRIBUTE]: '' } : {};
585
722
  }
@@ -654,13 +791,17 @@ function accordionPartStyle(node, name) {
654
791
  * paths render the same markup). It reads the markup the renderers write (iox-design.css + accordion.ts's
655
792
  * attribute planners), so the server's first paint and every later state agree:
656
793
  *
657
- * [root] data-iox-composite="accordion" data-iox-default-on [data-iox-multiple] (the node's own element —
658
- * its role class may sit on a wrapper host, as the engine's component-path Repeater draws it)
794
+ * [root] data-iox-composite="accordion" data-iox-default-on [data-iox-multiple] [data-iox-scope] (the node's
795
+ * own element — its role class may sit on a wrapper host, as the engine's component-path Repeater draws it)
659
796
  * .iox-role-accordion-item data-iox-item-key [data-iox-on]
660
797
  * hN.iox-accordion-heading > button.iox-accordion-button id aria-expanded aria-controls
661
798
  * .iox-accordion-track [data-iox-on] > .iox-accordion-region role=region [hidden=until-found]
662
799
  * .iox-role-accordion-panel
663
800
  *
801
+ * The DISCLOSURE (CC2.1) runs on this same runtime: `data-iox-composite="disclosure"` on a root that IS its one
802
+ * item (it carries the key and `data-iox-on`), its button straight inside it (no heading), its panel in the same
803
+ * track + region (a plain block — no `role=region`), and no arrow keys (one button).
804
+ *
664
805
  * It owns: a click on a header toggles its item; ↓ ↑ Home End move between the headers of THIS accordion
665
806
  * (a nested one keeps its own); find-in-page (`beforematch`) opens the item that holds the match; a closed
666
807
  * panel gets `hidden="until-found"` after its track has collapsed (at once under reduced motion). Every
@@ -670,23 +811,31 @@ function accordionPartStyle(node, name) {
670
811
  */
671
812
  const ITEM_ON_EVENT = 'iox-item-on';
672
813
  const ITEM_OFF_EVENT = 'iox-item-off';
673
- const ROOT_SELECTOR = `[${COMPOSITE_ATTRIBUTE}="${AccordionRole.Accordion}"]`;
674
- const ITEM_SELECTOR = '.iox-role-accordion-item';
675
- const PANEL_SELECTOR = '.iox-role-accordion-panel';
814
+ const KIND_ROOT = (kind) => `[${COMPOSITE_ATTRIBUTE}="${kind}"]`;
815
+ const ROOT_SELECTOR = Object.values(CompositeKind).map(KIND_ROOT).join(', ');
816
+ /** An accordion's items are its `.iox-role-accordion-item` children; a disclosure is its own item. */
817
+ const ACCORDION_ITEM_SELECTOR = '.iox-role-accordion-item';
818
+ const ITEM_SELECTOR = `${ACCORDION_ITEM_SELECTOR}, ${KIND_ROOT(CompositeKind.Disclosure)}`;
819
+ const PANEL_SELECTOR = '.iox-role-accordion-panel, .iox-role-disclosure-panel';
676
820
  const BUTTON_SELECTOR = `.${AccordionClass.Button}`;
677
821
  const TRACK_SELECTOR = `.${AccordionClass.Track}`;
678
822
  const REGION_SELECTOR = `.${AccordionClass.Region}`;
679
823
  /** How long a closing track may take before its region is hidden (dur-base is 200–320ms in every pack). */
680
824
  const MAX_CLOSE_MS = 600;
681
825
  const REDUCED_MOTION = '(prefers-reduced-motion: reduce)';
682
- /** The accordion an element belongs to (the element itself when it is a root). */
826
+ /** The composite (accordion or disclosure) an element belongs to (the element itself when it is a root). */
683
827
  function closestAccordion(el) {
684
828
  return el?.closest?.(ROOT_SELECTOR) ?? null;
685
829
  }
686
- /** The item an element sits in (itself when it is one). */
830
+ /** The item an element sits in (itself when it is one) — an accordion item, or a disclosure. */
687
831
  function closestAccordionItem(el) {
688
832
  return el?.closest?.(ITEM_SELECTOR) ?? null;
689
833
  }
834
+ const kindOf = (root) => root.getAttribute(COMPOSITE_ATTRIBUTE) === CompositeKind.Disclosure ? CompositeKind.Disclosure : CompositeKind.Accordion;
835
+ /** The composite root whose items an item belongs to: a disclosure is its own root. */
836
+ function rootOfItem(item) {
837
+ return kindOf(item) === CompositeKind.Disclosure && item.matches(KIND_ROOT(CompositeKind.Disclosure)) ? item : closestAccordion(item.parentElement);
838
+ }
690
839
  /** Subscribe to an item turning on / off — what the `itemOn` / `itemOff` interaction triggers listen to. The
691
840
  * item is the element's own item (the element itself when it is one). Returns the unsubscribe. */
692
841
  function onAccordionItemState(el, on, handler) {
@@ -745,6 +894,9 @@ class AccordionRuntime {
745
894
  this.toggle(this.keyOf(item));
746
895
  };
747
896
  this.onKeydown = (event) => {
897
+ // A disclosure has one button: Enter / Space are the button's own, and there is nowhere to move.
898
+ if (this.kind !== CompositeKind.Accordion)
899
+ return;
748
900
  const e = event;
749
901
  const action = accordionKeyAction(e.key);
750
902
  const button = e.target?.closest?.(BUTTON_SELECTOR);
@@ -761,12 +913,15 @@ class AccordionRuntime {
761
913
  this.onBeforeMatch = (event) => {
762
914
  const region = event.target?.closest?.(REGION_SELECTOR);
763
915
  const item = closestAccordionItem(region);
764
- if (!item || closestAccordion(item.parentElement) !== this.root)
916
+ const items = this.items();
917
+ if (!item || !items.includes(item))
765
918
  return;
766
- this.open(this.keyOf(item));
919
+ this.open(this.keyOf(item, items));
767
920
  };
921
+ this.kind = options.kind ?? kindOf(root);
768
922
  this.behaviourValue = options.behaviour ?? behaviourOf(root);
769
- root.setAttribute(COMPOSITE_ATTRIBUTE, AccordionRole.Accordion);
923
+ root.setAttribute(COMPOSITE_ATTRIBUTE, this.kind);
924
+ RUNTIMES.set(root, this);
770
925
  // The server rendered the first state; with no server state the root's default decides.
771
926
  // (The builder passes the behaviour and starts from the saved default; on the live site the server
772
927
  // stamped every item's key and state, and that is the truth.)
@@ -805,9 +960,11 @@ class AccordionRuntime {
805
960
  else
806
961
  this.stamp();
807
962
  }
808
- /** THIS accordion's items, in document order (a nested accordion's items are its own). */
963
+ /** THIS composite's items, in document order (a nested composite's items are its own); a disclosure's is itself. */
809
964
  items() {
810
- return Array.from(this.root.querySelectorAll(ITEM_SELECTOR)).filter(i => closestAccordion(i.parentElement) === this.root);
965
+ if (this.kind === CompositeKind.Disclosure)
966
+ return [this.root];
967
+ return Array.from(this.root.querySelectorAll(ACCORDION_ITEM_SELECTOR)).filter(i => closestAccordion(i.parentElement) === this.root);
811
968
  }
812
969
  /** The open keys the live state holds (before anything forced). */
813
970
  get liveKeys() {
@@ -828,6 +985,20 @@ class AccordionRuntime {
828
985
  this.live = openAccordionItem(this.live, key, this.behaviourValue);
829
986
  this.stamp();
830
987
  }
988
+ close(key) {
989
+ this.live = closeAccordionItem(this.live, key);
990
+ this.stamp();
991
+ }
992
+ /** Run an item action (`openItem` / `closeItem` / `toggleItem`, T14) on `key`. True when it shows open after. */
993
+ run(action, key) {
994
+ if (action === CompositeItemAction.Open)
995
+ this.open(key);
996
+ else if (action === CompositeItemAction.Close)
997
+ this.close(key);
998
+ else
999
+ this.toggle(key);
1000
+ return this.shown.has(key);
1001
+ }
831
1002
  /** Back to the saved default ("Showing on canvas ▸ As saved"). */
832
1003
  resetLive() {
833
1004
  const items = this.items();
@@ -839,6 +1010,8 @@ class AccordionRuntime {
839
1010
  this.stamp();
840
1011
  }
841
1012
  destroy() {
1013
+ if (RUNTIMES.get(this.root) === this)
1014
+ RUNTIMES.delete(this.root);
842
1015
  this.root.removeEventListener('click', this.onClick, true);
843
1016
  this.root.removeEventListener('keydown', this.onKeydown);
844
1017
  this.root.removeEventListener('beforematch', this.onBeforeMatch, true);
@@ -878,7 +1051,8 @@ class AccordionRuntime {
878
1051
  first = true;
879
1052
  }
880
1053
  const forced = this.options.forced?.() ?? new Set();
881
- const rootId = this.options.rootId ?? nodeIdOf(this.root);
1054
+ const own = this.options.rootId;
1055
+ const rootId = (typeof own === 'function' ? own() : own) ?? this.root.getAttribute(SCOPE_ATTRIBUTE) ?? nodeIdOf(this.root);
882
1056
  const next = new Set();
883
1057
  items.forEach((item, index) => {
884
1058
  const key = this.keyOf(item, items);
@@ -901,7 +1075,7 @@ class AccordionRuntime {
901
1075
  }
902
1076
  }
903
1077
  stampItem(item, key, index, on, rootId, first) {
904
- const ids = accordionDomIds(rootId, key);
1078
+ const ids = accordionDomIds(rootId, key, this.kind);
905
1079
  if (!item.hasAttribute(ITEM_KEY_ATTRIBUTE))
906
1080
  item.setAttribute(ITEM_KEY_ATTRIBUTE, key);
907
1081
  setAttr(item, ON_ATTRIBUTE, on ? '' : null);
@@ -916,9 +1090,10 @@ class AccordionRuntime {
916
1090
  setAttr(button, ON_ATTRIBUTE, on ? '' : null);
917
1091
  setAttr(track, ON_ATTRIBUTE, on ? '' : null);
918
1092
  setAttr(panel, ON_ATTRIBUTE, on ? '' : null);
919
- setAttr(region, 'role', 'region');
1093
+ const labelled = hasLabelledRegion(this.kind);
1094
+ setAttr(region, 'role', labelled ? 'region' : null);
920
1095
  setAttr(region, 'id', ids.panel);
921
- setAttr(region, 'aria-labelledby', ids.trigger);
1096
+ setAttr(region, 'aria-labelledby', labelled ? ids.trigger : null);
922
1097
  if (!region)
923
1098
  return;
924
1099
  const pending = this.closing.get(key);
@@ -969,14 +1144,12 @@ function dispatch(el, type) {
969
1144
  if (Ctor)
970
1145
  el.dispatchEvent(new Ctor(type, { bubbles: false }));
971
1146
  }
972
- /** Attach the runtime to one rendered accordion root, unless it already runs one. Returns the NEW runtime, or
1147
+ /** Attach the runtime to one rendered composite root, unless it already runs one. Returns the NEW runtime, or
973
1148
  * null when the root already had one (so a caller destroys only what it created). */
974
1149
  function attachAccordionAt(root, options = {}) {
975
- if (ATTACHED.has(root))
1150
+ if (RUNTIMES.has(root))
976
1151
  return null;
977
- const runtime = new AccordionRuntime(root, options);
978
- ATTACHED.set(root, runtime);
979
- return runtime;
1152
+ return new AccordionRuntime(root, options);
980
1153
  }
981
1154
  /** Attach the runtime to every accordion under `container` that has none yet. Returns the new runtimes. */
982
1155
  function attachAccordions(container, options = {}) {
@@ -991,9 +1164,134 @@ function attachAccordions(container, options = {}) {
991
1164
  /** Detach a runtime `attachAccordions` created. */
992
1165
  function detachAccordion(runtime) {
993
1166
  runtime.destroy();
994
- ATTACHED.delete(runtime.root);
995
1167
  }
996
- const ATTACHED = new WeakMap();
1168
+ /** Every live runtime by its root — the canvas's and the site's alike (a runtime registers itself and leaves on
1169
+ * destroy), so an item action finds the runtime of whatever item it targets. */
1170
+ const RUNTIMES = new WeakMap();
1171
+ const ROW_INDEX_ATTRIBUTE = 'data-iox-row-idx';
1172
+ /** The repeated rows an element sits in, outermost first — each row's index as its row wrappers stamp it
1173
+ * (`data-iox-row-idx`: the builder's Repeater rows, the engine's component path). The builder folds them into a
1174
+ * composite's scope id (`compositeScopeId`, vectoriox/iox-ui#20); the element itself is not counted. */
1175
+ function compositeRowKeys(el) {
1176
+ const keys = [];
1177
+ for (let at = el?.parentElement?.closest?.(`[${ROW_INDEX_ATTRIBUTE}]`); at; at = at.parentElement?.closest(`[${ROW_INDEX_ATTRIBUTE}]`)) {
1178
+ keys.unshift(at.getAttribute(ROW_INDEX_ATTRIBUTE) ?? '');
1179
+ }
1180
+ return keys;
1181
+ }
1182
+ /** The runtime running a composite root, or null. */
1183
+ function accordionRuntimeOf(root) {
1184
+ return (root && RUNTIMES.get(root)) || null;
1185
+ }
1186
+ // ── T14: open / close / toggle an item from anywhere ─────────────────────────────────────────────
1187
+ /** What an item action does to its target item (actions-core `openItem` / `closeItem` / `toggleItem`). */
1188
+ var CompositeItemAction;
1189
+ (function (CompositeItemAction) {
1190
+ CompositeItemAction["Open"] = "open";
1191
+ CompositeItemAction["Close"] = "close";
1192
+ CompositeItemAction["Toggle"] = "toggle";
1193
+ })(CompositeItemAction || (CompositeItemAction = {}));
1194
+ /**
1195
+ * Run an item action on the element of an ITEM (an accordion item, or a disclosure) — the `openItem` /
1196
+ * `closeItem` / `toggleItem` interaction actions, on the canvas and on the live site through this one function.
1197
+ * Returns whether the item shows open afterwards, or null when `el` is in no running composite.
1198
+ */
1199
+ function runCompositeItemAction(el, action) {
1200
+ const item = closestAccordionItem(el);
1201
+ const runtime = item ? accordionRuntimeOf(rootOfItem(item)) : null;
1202
+ if (!item || !runtime)
1203
+ return null;
1204
+ return runtime.run(action, runtime.keyOf(item));
1205
+ }
1206
+ /** How much of an opened item must already be on screen for the action NOT to scroll to it (its header row). */
1207
+ const VISIBLE_HEAD_PX = 48;
1208
+ /** True when an item an action just opened is out of view and the page should scroll to it (T14: "opens that item
1209
+ * and scrolls to it") — never when its head is already on screen, so a button beside it does not jolt the page. */
1210
+ function shouldScrollToItem(top, viewportHeight) {
1211
+ return top < 0 || top + VISIBLE_HEAD_PX > viewportHeight;
1212
+ }
1213
+
1214
+ /** The words a fresh disclosure starts with — generic, the author's to replace. */
1215
+ const DISCLOSURE_WORDS = {
1216
+ more: 'Read more',
1217
+ less: 'Read less',
1218
+ body: 'Write the rest of the story here. It stays out of the way until someone asks for it.',
1219
+ };
1220
+ /** A disclosure's one item key — its root IS its item. */
1221
+ const DISCLOSURE_ITEM_KEY = 'k1';
1222
+ var Type$1;
1223
+ (function (Type) {
1224
+ Type["Container"] = "Container";
1225
+ Type["LinkedContainer"] = "LinkedContainer";
1226
+ Type["Text"] = "Text";
1227
+ })(Type$1 || (Type$1 = {}));
1228
+ var Style$1;
1229
+ (function (Style) {
1230
+ Style["Root"] = "box";
1231
+ Style["Trigger"] = "accordion-trigger";
1232
+ Style["Word"] = "body";
1233
+ Style["Panel"] = "accordion-panel";
1234
+ Style["Body"] = "body";
1235
+ })(Style$1 || (Style$1 = {}));
1236
+ const ZERO_BOX$1 = '0px 0px 0px 0px';
1237
+ const HIDDEN = 'none';
1238
+ const INHERIT = 'inherit';
1239
+ /** A word is a flex item of the toggle — `block` (never `inline`: the compiler drops a value equal to the CSS initial,
1240
+ * and the Text's own default would come back in its place). */
1241
+ const SHOWN = 'block';
1242
+ /** What each part holds — layout only; its LOOK is its element style. The trigger sizes to its words (a button in
1243
+ * the flow, not a full-width header row — `fit-content` keeps it from stretching) and the panel spans the root. The
1244
+ * trigger is `flex`, not `inline-flex`: it is a flex item of the root, which blockifies it on the site anyway. */
1245
+ const PART_BASE = {
1246
+ [DisclosureRole.Disclosure]: { display: 'flex', flexDirection: 'column', width: '100%', minHeight: 'auto', height: 'auto' },
1247
+ // The toggle reads the text around it (the page's body type, through the root) — a LinkedContainer is born with
1248
+ // its own literal font, which an accordion's heading masks and a disclosure's words would show.
1249
+ [DisclosureRole.Trigger]: {
1250
+ display: 'flex', alignItems: 'center', gap: '8px', width: 'fit-content', minHeight: 'auto', height: 'auto', textAlign: 'start', cursor: 'pointer',
1251
+ fontFamily: INHERIT, fontSize: INHERIT, fontWeight: INHERIT, lineHeight: INHERIT,
1252
+ },
1253
+ // A word is just its words: none of a Text's own box (its type's 10px padding, its full width) — the toggle's
1254
+ // padding is the click target.
1255
+ [DisclosureRole.More]: { display: SHOWN, margin: ZERO_BOX$1, padding: ZERO_BOX$1, width: 'auto' },
1256
+ [DisclosureRole.Less]: { display: HIDDEN, margin: ZERO_BOX$1, padding: ZERO_BOX$1, width: 'auto' },
1257
+ [DisclosureRole.Panel]: { display: 'block', width: '100%', minHeight: 'auto', height: 'auto' },
1258
+ };
1259
+ const BODY_BASE = { margin: ZERO_BOX$1, padding: ZERO_BOX$1, minHeight: 'auto' };
1260
+ function make$1(create, key, type, role, style, props, label) {
1261
+ return makeCompositePart(create, { key, type, props, elementStyle: style }, role, label, role ? PART_BASE[role] : undefined);
1262
+ }
1263
+ /** One of the trigger's two words: shown while the disclosure is closed (`more`) or open (`less`). */
1264
+ function buildWord(create, role, words, rootId) {
1265
+ const node = make$1(create, role === DisclosureRole.More ? 'more' : 'less', Type$1.Text, role, Style$1.Word, { content: words, tag: 'span' }, 'Text');
1266
+ node.stateStyles = { ...(node.stateStyles ?? {}), [ITEM_OPEN_STATE]: { display: role === DisclosureRole.More ? HIDDEN : SHOWN } };
1267
+ if (rootId)
1268
+ node.stateTriggers = { ...(node.stateTriggers ?? {}), [ITEM_OPEN_STATE]: rootId };
1269
+ return node;
1270
+ }
1271
+ /** A fresh Disclosure: "Read more" opens the text below and reads "Read less" while it is open; closed by default. */
1272
+ function buildDisclosureRecipe(create, options = {}) {
1273
+ const words = { ...DISCLOSURE_WORDS, ...(options.words ?? {}) };
1274
+ const root = make$1(create, 'disclosure', Type$1.Container, DisclosureRole.Disclosure, Style$1.Root, {}, 'Disclosure');
1275
+ root.itemKey = DISCLOSURE_ITEM_KEY;
1276
+ writeProp(root, CompositeBehaviourTrait.DefaultOn, options.open ? AccordionDefaultOn.First : AccordionDefaultOn.None);
1277
+ const icon = options.icon ?? AccordionIcon.Chevron;
1278
+ const trigger = make$1(create, 'trigger', Type$1.LinkedContainer, DisclosureRole.Trigger, Style$1.Trigger, {}, 'Toggle');
1279
+ trigger.children = [
1280
+ buildWord(create, DisclosureRole.More, words.more, root.id),
1281
+ buildWord(create, DisclosureRole.Less, words.less, root.id),
1282
+ ...(icon === AccordionIcon.None ? [] : [buildCompositeIcon(create, 'icon', icon, root.id, DisclosureRole.Icon)]),
1283
+ ];
1284
+ const panel = make$1(create, 'panel', Type$1.Container, DisclosureRole.Panel, Style$1.Panel, {}, 'Panel');
1285
+ const body = make$1(create, 'body', Type$1.Text, null, Style$1.Body, { content: words.body, tag: 'p' }, 'Text');
1286
+ writeStyles(body, BODY_BASE);
1287
+ panel.children = [body];
1288
+ root.children = [trigger, panel];
1289
+ return root;
1290
+ }
1291
+ /** Behaviour ▸ Open by default (a disclosure: on or off). */
1292
+ function disclosureOpenByDefault(root) {
1293
+ return accordionBehaviour(root).defaultOn !== AccordionDefaultOn.None;
1294
+ }
997
1295
 
998
1296
  /**
999
1297
  * BADGE + CHIP (composite T25; complex-components board 8). A Badge is a Container wearing
@@ -1552,6 +1850,13 @@ const COMPOSITE_ROLE_SPECS = {
1552
1850
  [AccordionRole.Trigger]: { parent: AccordionRole.Item, locked: true },
1553
1851
  [AccordionRole.Panel]: { parent: AccordionRole.Item, locked: true },
1554
1852
  [AccordionRole.Icon]: { parent: AccordionRole.Trigger },
1853
+ // CC2.1 — a disclosure always keeps its toggle and its panel; the words and the icon are the author's.
1854
+ [DisclosureRole.Disclosure]: { parent: null },
1855
+ [DisclosureRole.Trigger]: { parent: DisclosureRole.Disclosure, locked: true },
1856
+ [DisclosureRole.Panel]: { parent: DisclosureRole.Disclosure, locked: true },
1857
+ [DisclosureRole.More]: { parent: DisclosureRole.Trigger },
1858
+ [DisclosureRole.Less]: { parent: DisclosureRole.Trigger },
1859
+ [DisclosureRole.Icon]: { parent: DisclosureRole.Trigger },
1555
1860
  };
1556
1861
  var StructureEdit;
1557
1862
  (function (StructureEdit) {
@@ -1590,7 +1895,8 @@ function isLockedPart(node) {
1590
1895
  * plan §3a). FRAMEWORK-FREE: the builder, the SSR engine and iox-cms (generation-core's composer, the site
1591
1896
  * build) read the same recipes, roles and structure rules. Card v2 + Badge / Chip (T23 / T25) are the
1592
1897
  * first; the Accordion (CC2) adds the first composite RUNTIME (accordion-runtime.ts — DOM only, no
1593
- * framework); Tabs join with their own phase.
1898
+ * framework), which the Disclosure (CC2.1) runs too — a one-item accordion whose root is its item; Tabs join with
1899
+ * their own phase.
1594
1900
  * ⚠️ Consumers wire the usual trio — ng-package raw-ts assets, jest moduleNameMapper, tsconfig paths.
1595
1901
  */
1596
1902
 
@@ -1598,5 +1904,5 @@ function isLockedPart(node) {
1598
1904
  * Generated bundle index. Do not edit.
1599
1905
  */
1600
1906
 
1601
- export { ACCORDION_BIND_PROP, ACCORDION_CONTENT_LIMIT, ACCORDION_DEFAULT_ONS, ACCORDION_ICONS, ACCORDION_ICON_GLYPH, ACCORDION_NEW_ITEM_WORDS, ACCORDION_WORDS, 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, CHIP_ICON, CHIP_STYLE, COMPOSITE_ATTRIBUTE, COMPOSITE_ROLE_SPECS, CardLayout, CardPart, CardRole, CompositeBehaviourTrait, DEFAULT_ON_ATTRIBUTE, ITEM_KEY_ATTRIBUTE, ITEM_OFF_EVENT, ITEM_ON_EVENT, ITEM_OPEN_STATE, MULTIPLE_ATTRIBUTE, ON_ATTRIBUTE, ON_STATE, PILL_LABEL_STYLE, StructureEdit, StructureRefusal, UNTIL_FOUND, accordionAnswerOf, accordionBehaviour, accordionDomIds, accordionFocusIndex, accordionHeadingLevel, accordionItemAttributes, accordionItemLabel, accordionItemOf, accordionItemState, accordionItems, accordionKeyAction, accordionPanelAttributes, accordionPanelOf, accordionPartStyle, accordionQuestionOf, accordionRegionAttributes, accordionRemovalRefusal, accordionRootAttributes, accordionRootOf, accordionScope, accordionToContent, accordionToManual, accordionTrackAttributes, accordionTriggerAttributes, accordionTriggerOf, addAccordionItem, applyCardLayout, attachAccordionAt, attachAccordions, badgeStyleId, badgeToneOf, buildAccordionItem, buildAccordionRecipe, buildBadge, buildCardRecipe, buildChip, cardPartsOf, cardRootOf, cardTitleOf, clearLayerStyles, closestAccordion, closestAccordionItem, detachAccordion, detectAccordionIcon, detectCardLayout, domSafeKey, findByRole, initialAccordionOpen, isAccordionFromContent, isAccordionItemOn, isAccordionPart, isAccordionRoot, isCardLinked, isCardRoot, isLockedPart, isPillNode, itemOpenTriggerIds, moveAccordionItem, nextAccordionState, nextItemKey, onAccordionItemState, openAccordionItem, parentInComposite, pickAccordionAutoBinds, readLayerStyle, readProp, readStyle, removeFromComposite, renameAccordionItem, rowItemKey, serializeDomAttributes, setAccordionBehaviour, setAccordionIcon, setBadgeTone, setCardLinked, setCardPart, structureRefusal, walkComposite, writeLayerStyles, writeProp, writeStyles };
1907
+ export { ACCORDION_BIND_PROP, ACCORDION_CONTENT_LIMIT, ACCORDION_DEFAULT_ONS, ACCORDION_ICONS, ACCORDION_ICON_GLYPH, ACCORDION_NEW_ITEM_WORDS, ACCORDION_WORDS, 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, CHIP_ICON, CHIP_STYLE, COMPOSITE_ATTRIBUTE, COMPOSITE_KIND_ROLES, COMPOSITE_ROLE_SPECS, CardLayout, CardPart, CardRole, CompositeBehaviourTrait, CompositeItemAction, CompositeKind, DEFAULT_ON_ATTRIBUTE, DISCLOSURE_ITEM_KEY, DISCLOSURE_WORDS, DisclosureRole, ITEM_KEY_ATTRIBUTE, ITEM_OFF_EVENT, ITEM_ON_EVENT, ITEM_OPEN_STATE, MULTIPLE_ATTRIBUTE, ON_ATTRIBUTE, ON_STATE, PILL_LABEL_STYLE, SCOPE_ATTRIBUTE, StructureEdit, StructureRefusal, UNTIL_FOUND, accordionAnswerOf, accordionBehaviour, accordionDomIds, accordionFocusIndex, accordionHeadingLevel, accordionItemAttributes, accordionItemLabel, accordionItemOf, accordionItemState, accordionItems, accordionKeyAction, accordionPanelAttributes, accordionPanelOf, accordionPartStyle, accordionQuestionOf, accordionRegionAttributes, accordionRemovalRefusal, accordionRootAttributes, accordionRootOf, accordionRuntimeOf, accordionScope, accordionToContent, accordionToManual, accordionTrackAttributes, accordionTriggerAttributes, accordionTriggerOf, addAccordionItem, applyCardLayout, attachAccordionAt, attachAccordions, badgeStyleId, badgeToneOf, buildAccordionItem, buildAccordionRecipe, buildBadge, buildCardRecipe, buildChip, buildCompositeIcon, buildDisclosureRecipe, cardPartsOf, cardRootOf, cardTitleOf, clearLayerStyles, closeAccordionItem, closestAccordion, closestAccordionItem, compositeItemOf, compositeItems, compositeKindOfRole, compositeRootOf, compositeRowKeys, compositeScopeId, detachAccordion, detectAccordionIcon, detectCardLayout, disclosureOpenByDefault, domSafeKey, findByRole, hasLabelledRegion, initialAccordionOpen, isAccordionFromContent, isAccordionItemOn, isAccordionPart, isAccordionRoot, isCardLinked, isCardRoot, isCompositeItem, isCompositePanel, isCompositeRoot, isCompositeTrigger, isDisclosurePart, isDisclosureRoot, isLockedPart, isPillNode, itemOpenTriggerIds, makeCompositePart, moveAccordionItem, nextAccordionState, nextItemKey, onAccordionItemState, openAccordionItem, parentInComposite, pickAccordionAutoBinds, readLayerStyle, readProp, readStyle, removeFromComposite, renameAccordionItem, rowItemKey, runCompositeItemAction, serializeDomAttributes, setAccordionBehaviour, setAccordionIcon, setBadgeTone, setCardLinked, setCardPart, shouldScrollToItem, structureRefusal, walkComposite, writeLayerStyles, writeProp, writeStyles };
1602
1908
  //# sourceMappingURL=vectoriox-iox-ui-composite-core.mjs.map