@vectoriox/iox-ui 5.38.0 → 5.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/composite-core/index.d.ts +392 -16
- package/design-core/index.d.ts +322 -3
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs +4 -2
- package/fesm2022/vectoriox-iox-ui-actions-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-composite-core.mjs +1129 -146
- package/fesm2022/vectoriox-iox-ui-composite-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-design-core.mjs +383 -3
- package/fesm2022/vectoriox-iox-ui-design-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs +125 -3
- package/fesm2022/vectoriox-iox-ui-generation-core.mjs.map +1 -1
- package/fesm2022/vectoriox-iox-ui.mjs +16 -9
- package/fesm2022/vectoriox-iox-ui.mjs.map +1 -1
- package/generation-core/index.d.ts +43 -0
- package/index.d.ts +11 -6
- package/package.json +1 -1
- package/src/actions-core/element-actions.ts +4 -2
- package/src/composite-core/accordion-runtime.ts +3 -15
- package/src/composite-core/accordion.ts +4 -2
- package/src/composite-core/composite-kinds.ts +121 -7
- package/src/composite-core/composite-runtime.ts +68 -0
- package/src/composite-core/exclusive-runtime.ts +358 -0
- package/src/composite-core/public-api.ts +5 -2
- package/src/composite-core/structure.ts +22 -2
- package/src/composite-core/tabs.ts +628 -0
- package/src/design-core/contrast-audit.ts +5 -0
- package/src/design-core/element-styles.ts +7 -3
- package/src/design-core/flavor-spec.generated.ts +371 -0
- package/src/generation-core/compose-site.ts +68 -1
- package/src/generation-core/node-baselines.generated.ts +8 -2
- package/src/generation-core/patterns.ts +3 -0
- package/src/generation-core/site-spec.generated.ts +46 -0
- package/styles/iox-design.css +16 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readOwnedTrait, writeOwnedTrait, elementStyleById, switchElementStyle, LinkType, setColorScheme, COLOR_SCHEME_TRAIT } from '@vectoriox/iox-ui/design-core';
|
|
1
|
+
import { readOwnedTrait, writeOwnedTrait, elementStyleById, switchElementStyle, LinkType, setColorScheme, COLOR_SCHEME_TRAIT, roleClass } from '@vectoriox/iox-ui/design-core';
|
|
2
2
|
|
|
3
3
|
/** What a recipe asks its consumer to create: a NEW node of `type`, the way that consumer creates one
|
|
4
4
|
* (the builder's `createConfig`, generation-core's `createSerializedNode`), with these props and this
|
|
@@ -125,15 +125,18 @@ function removeFromComposite(root, node) {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
|
-
* The
|
|
129
|
-
*
|
|
130
|
-
* same attribute planners and
|
|
131
|
-
*
|
|
128
|
+
* The composites' kinds and roles. Two families:
|
|
129
|
+
* - OPEN/CLOSE (CC2 Accordion, CC2.1 Disclosure — composite plan T9 / T21): a Disclosure is a one-item accordion whose
|
|
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 joins in CC4b): exactly one item is on, a trigger and
|
|
132
|
+
* its panel paired by `itemKey` (`tabs.ts`, `exclusive-runtime.ts`).
|
|
133
|
+
* Every consumer that treats "an item", "a trigger" or "a panel" alike asks these predicates, never a role literal.
|
|
132
134
|
*/
|
|
133
135
|
var CompositeKind;
|
|
134
136
|
(function (CompositeKind) {
|
|
135
137
|
CompositeKind["Accordion"] = "accordion";
|
|
136
138
|
CompositeKind["Disclosure"] = "disclosure";
|
|
139
|
+
CompositeKind["Tabs"] = "tabs";
|
|
137
140
|
})(CompositeKind || (CompositeKind = {}));
|
|
138
141
|
var AccordionRole;
|
|
139
142
|
(function (AccordionRole) {
|
|
@@ -157,21 +160,53 @@ var DisclosureRole;
|
|
|
157
160
|
DisclosureRole["Icon"] = "disclosure.icon";
|
|
158
161
|
DisclosureRole["Panel"] = "disclosure.panel";
|
|
159
162
|
})(DisclosureRole || (DisclosureRole = {}));
|
|
163
|
+
/**
|
|
164
|
+
* TABS (CC4a — composite T17). The tab LIST holds the triggers, the PANELS holder holds the panels; a tab and its panel
|
|
165
|
+
* share an `itemKey` (never their index — moving a tab keeps its own panel). From content (R3) the list and the panels
|
|
166
|
+
* holder are both Repeaters over the SAME source, one row giving its tab AND its panel (paired by the row's id).
|
|
167
|
+
*/
|
|
168
|
+
var TabsRole;
|
|
169
|
+
(function (TabsRole) {
|
|
170
|
+
TabsRole["Tabs"] = "tabs";
|
|
171
|
+
TabsRole["List"] = "tabs.list";
|
|
172
|
+
TabsRole["Trigger"] = "tabs.trigger";
|
|
173
|
+
TabsRole["Panels"] = "tabs.panels";
|
|
174
|
+
TabsRole["Panel"] = "tabs.panel";
|
|
175
|
+
})(TabsRole || (TabsRole = {}));
|
|
160
176
|
const COMPOSITE_KIND_ROLES = {
|
|
161
177
|
[CompositeKind.Accordion]: { root: AccordionRole.Accordion, item: AccordionRole.Item, trigger: AccordionRole.Trigger, panel: AccordionRole.Panel, icon: AccordionRole.Icon },
|
|
162
178
|
[CompositeKind.Disclosure]: { root: DisclosureRole.Disclosure, item: DisclosureRole.Disclosure, trigger: DisclosureRole.Trigger, panel: DisclosureRole.Panel, icon: DisclosureRole.Icon },
|
|
163
179
|
};
|
|
180
|
+
const EXCLUSIVE_KIND_ROLES = {
|
|
181
|
+
[CompositeKind.Tabs]: { root: TabsRole.Tabs, list: TabsRole.List, trigger: TabsRole.Trigger, panels: TabsRole.Panels, panel: TabsRole.Panel },
|
|
182
|
+
};
|
|
164
183
|
const KINDS = Object.values(CompositeKind);
|
|
165
|
-
const
|
|
184
|
+
const TOGGLE_KINDS = Object.keys(COMPOSITE_KIND_ROLES);
|
|
185
|
+
const EXCLUSIVE_KINDS$1 = Object.keys(EXCLUSIVE_KIND_ROLES);
|
|
186
|
+
const rolesOf = (pick) => new Set(TOGGLE_KINDS.map(k => COMPOSITE_KIND_ROLES[k][pick]));
|
|
187
|
+
const exclusiveRolesOf = (pick) => new Set(EXCLUSIVE_KINDS$1.map(k => EXCLUSIVE_KIND_ROLES[k][pick]).filter((r) => !!r));
|
|
166
188
|
const ROOT_ROLES = rolesOf('root');
|
|
167
189
|
const ITEM_ROLES = rolesOf('item');
|
|
168
190
|
const TRIGGER_ROLES = rolesOf('trigger');
|
|
169
191
|
const PANEL_ROLES = rolesOf('panel');
|
|
192
|
+
const EXCLUSIVE_ROOT_ROLES = exclusiveRolesOf('root');
|
|
193
|
+
const EXCLUSIVE_LIST_ROLES = exclusiveRolesOf('list');
|
|
194
|
+
const EXCLUSIVE_TRIGGER_ROLES = exclusiveRolesOf('trigger');
|
|
195
|
+
const EXCLUSIVE_PANELS_ROLES = exclusiveRolesOf('panels');
|
|
196
|
+
const EXCLUSIVE_PANEL_ROLES = exclusiveRolesOf('panel');
|
|
170
197
|
/** The kind a role belongs to (`disclosure.panel` → Disclosure), or null for any other role. */
|
|
171
198
|
function compositeKindOfRole(role) {
|
|
172
199
|
const head = role?.split('.')[0];
|
|
173
200
|
return KINDS.includes(head ?? '') ? head : null;
|
|
174
201
|
}
|
|
202
|
+
/** True for a kind the open/close runtime runs. */
|
|
203
|
+
function isToggleKind(kind) {
|
|
204
|
+
return !!kind && TOGGLE_KINDS.includes(kind);
|
|
205
|
+
}
|
|
206
|
+
/** True for a kind the exclusive runtime runs. */
|
|
207
|
+
function isExclusiveKind(kind) {
|
|
208
|
+
return !!kind && EXCLUSIVE_KINDS$1.includes(kind);
|
|
209
|
+
}
|
|
175
210
|
/** A root that runs the open/close runtime (an Accordion or a Disclosure). */
|
|
176
211
|
function isCompositeRoot(node) {
|
|
177
212
|
return !!node?.role && ROOT_ROLES.has(node.role);
|
|
@@ -218,6 +253,61 @@ function compositeItemOf(node, path) {
|
|
|
218
253
|
}
|
|
219
254
|
return null;
|
|
220
255
|
}
|
|
256
|
+
// ── the exclusive family (CC4a Tabs) ───────────────────────────────────────────────────────────
|
|
257
|
+
/** A root that runs the exclusive runtime (Tabs). */
|
|
258
|
+
function isExclusiveRoot(node) {
|
|
259
|
+
return !!node?.role && EXCLUSIVE_ROOT_ROLES.has(node.role);
|
|
260
|
+
}
|
|
261
|
+
/** The holder of an exclusive composite's triggers (a tab list): it takes only triggers. */
|
|
262
|
+
function isExclusiveList(node) {
|
|
263
|
+
return !!node?.role && EXCLUSIVE_LIST_ROLES.has(node.role);
|
|
264
|
+
}
|
|
265
|
+
/** One of an exclusive composite's triggers (a tab): pressing it turns its item on. */
|
|
266
|
+
function isExclusiveTrigger(node) {
|
|
267
|
+
return !!node?.role && EXCLUSIVE_TRIGGER_ROLES.has(node.role);
|
|
268
|
+
}
|
|
269
|
+
/** The holder of an exclusive composite's panels: it takes only panels. */
|
|
270
|
+
function isExclusivePanels(node) {
|
|
271
|
+
return !!node?.role && EXCLUSIVE_PANELS_ROLES.has(node.role);
|
|
272
|
+
}
|
|
273
|
+
/** One of an exclusive composite's panels (a tab panel): shown only while its item is on. */
|
|
274
|
+
function isExclusivePanel(node) {
|
|
275
|
+
return !!node?.role && EXCLUSIVE_PANEL_ROLES.has(node.role);
|
|
276
|
+
}
|
|
277
|
+
/** True for a trigger or a panel — a part that IS one item of an exclusive composite (it carries the `itemKey`). */
|
|
278
|
+
function isExclusiveItemPart(node) {
|
|
279
|
+
return isExclusiveTrigger(node) || isExclusivePanel(node);
|
|
280
|
+
}
|
|
281
|
+
/** True for any part of a Tabs (its root included). */
|
|
282
|
+
function isTabsPart(node) {
|
|
283
|
+
return compositeKindOfRole(node?.role) === CompositeKind.Tabs;
|
|
284
|
+
}
|
|
285
|
+
/** A root of either family — anything that runs a composite runtime. */
|
|
286
|
+
function isAnyCompositeRoot(node) {
|
|
287
|
+
return isCompositeRoot(node) || isExclusiveRoot(node);
|
|
288
|
+
}
|
|
289
|
+
/** The exclusive root a node belongs to (the node itself when it is one), or null. `path` = its ancestors, root first. */
|
|
290
|
+
function exclusiveRootOf(node, path) {
|
|
291
|
+
if (isExclusiveRoot(node))
|
|
292
|
+
return node;
|
|
293
|
+
for (let i = path.length - 1; i >= 0; i--)
|
|
294
|
+
if (isExclusiveRoot(path[i]))
|
|
295
|
+
return path[i];
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
/** The trigger or panel a node sits in (itself when it is one) inside an exclusive composite, or null. A nested
|
|
299
|
+
* exclusive root ends the walk. */
|
|
300
|
+
function exclusiveItemPartOf(node, path) {
|
|
301
|
+
if (isExclusiveItemPart(node))
|
|
302
|
+
return node;
|
|
303
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
304
|
+
if (isExclusiveItemPart(path[i]))
|
|
305
|
+
return path[i];
|
|
306
|
+
if (isExclusiveRoot(path[i]))
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
221
311
|
|
|
222
312
|
/**
|
|
223
313
|
* ACCORDION (CC2 — composite K1; complex-components boards 3, 4 and 9, APPROVED 2026-10-20). Not a
|
|
@@ -278,7 +368,7 @@ const ACCORDION_WORDS = [
|
|
|
278
368
|
{ question: 'Write the third question', answer: 'Answer it plainly, in two or three short sentences.' },
|
|
279
369
|
];
|
|
280
370
|
const ACCORDION_NEW_ITEM_WORDS = { question: 'Write a new question', answer: 'Answer it plainly, in two or three short sentences.' };
|
|
281
|
-
var Type$
|
|
371
|
+
var Type$4;
|
|
282
372
|
(function (Type) {
|
|
283
373
|
Type["Container"] = "Container";
|
|
284
374
|
Type["LinkedContainer"] = "LinkedContainer";
|
|
@@ -286,8 +376,8 @@ var Type$3;
|
|
|
286
376
|
Type["Text"] = "Text";
|
|
287
377
|
Type["Icon"] = "Icon";
|
|
288
378
|
Type["Repeater"] = "Repeater";
|
|
289
|
-
})(Type$
|
|
290
|
-
var Style$
|
|
379
|
+
})(Type$4 || (Type$4 = {}));
|
|
380
|
+
var Style$4;
|
|
291
381
|
(function (Style) {
|
|
292
382
|
Style["Root"] = "box";
|
|
293
383
|
Style["Item"] = "accordion-item";
|
|
@@ -296,14 +386,14 @@ var Style$3;
|
|
|
296
386
|
Style["Icon"] = "icon";
|
|
297
387
|
Style["Panel"] = "accordion-panel";
|
|
298
388
|
Style["Answer"] = "body";
|
|
299
|
-
})(Style$
|
|
300
|
-
const ZERO_BOX$
|
|
389
|
+
})(Style$4 || (Style$4 = {}));
|
|
390
|
+
const ZERO_BOX$3 = '0px 0px 0px 0px';
|
|
301
391
|
const HEADING_LEVELS = ['h2', 'h3', 'h4', 'h5', 'h6'];
|
|
302
392
|
const DEFAULT_LEVEL = 'h3';
|
|
303
393
|
/** A linked trait the header must never carry: it is a button, not a link. */
|
|
304
394
|
const LINK_TYPE$1 = 'linkType';
|
|
305
395
|
/** What each part holds in the recipe — layout only; its LOOK is its element style. */
|
|
306
|
-
const PART_BASE$
|
|
396
|
+
const PART_BASE$2 = {
|
|
307
397
|
// The items are a column a token apart (`accordion-gap`: 0 for a divider list, 12px for cards).
|
|
308
398
|
[AccordionRole.Accordion]: { display: 'flex', flexDirection: 'column', gap: 'var(--iox-accordion-gap)', width: '100%', minHeight: 'auto', height: 'auto' },
|
|
309
399
|
[AccordionRole.Item]: { display: 'block', width: '100%', minHeight: 'auto', height: 'auto' },
|
|
@@ -314,16 +404,16 @@ const PART_BASE$1 = {
|
|
|
314
404
|
};
|
|
315
405
|
/** The question INHERITS the header's ink (board 4's wears table: the Question reads no colour token), so the
|
|
316
406
|
* header's hover ink reaches the words; and it sits in the row with no margin of its own. */
|
|
317
|
-
const QUESTION_BASE = { color: 'inherit', margin: ZERO_BOX$
|
|
318
|
-
const ANSWER_BASE = { margin: ZERO_BOX$
|
|
319
|
-
function make$
|
|
320
|
-
return makeCompositePart(create, { key, type, props, elementStyle: style }, role, label, role ? PART_BASE$
|
|
407
|
+
const QUESTION_BASE = { color: 'inherit', margin: ZERO_BOX$3, flexGrow: '1', minWidth: '0px' };
|
|
408
|
+
const ANSWER_BASE = { margin: ZERO_BOX$3, padding: ZERO_BOX$3, minHeight: 'auto' };
|
|
409
|
+
function make$3(create, key, type, role, style, props = {}, label) {
|
|
410
|
+
return makeCompositePart(create, { key, type, props, elementStyle: style }, role, label, role ? PART_BASE$2[role] : undefined);
|
|
321
411
|
}
|
|
322
412
|
/** The icon of an item (an accordion's, or a disclosure's — `role`); its Item-open state (the turn) is driven by
|
|
323
413
|
* ITS item (`stateTriggers`). ONE builder for both kinds. */
|
|
324
414
|
function buildCompositeIcon(create, key, icon, itemId, role = AccordionRole.Icon) {
|
|
325
415
|
const glyph = ACCORDION_ICON_GLYPH[icon];
|
|
326
|
-
const node = makeCompositePart(create, { key, type: Type$
|
|
416
|
+
const node = makeCompositePart(create, { key, type: Type$4.Icon, props: { iconClass: glyph.iconClass }, elementStyle: Style$4.Icon }, role, 'Icon', PART_BASE$2[AccordionRole.Icon]);
|
|
327
417
|
node.stateStyles = { ...(node.stateStyles ?? {}), [ITEM_OPEN_STATE]: { rotate: glyph.turn } };
|
|
328
418
|
if (itemId)
|
|
329
419
|
node.stateTriggers = { ...(node.stateTriggers ?? {}), [ITEM_OPEN_STATE]: itemId };
|
|
@@ -331,14 +421,14 @@ function buildCompositeIcon(create, key, icon, itemId, role = AccordionRole.Icon
|
|
|
331
421
|
}
|
|
332
422
|
/** One item: header (question + icon) and panel (answer). `key` makes every part's recipe key unique. */
|
|
333
423
|
function buildAccordionItem(create, words, itemKey, icon = AccordionIcon.Chevron) {
|
|
334
|
-
const item = make$
|
|
424
|
+
const item = make$3(create, `${itemKey}`, Type$4.Container, AccordionRole.Item, Style$4.Item, {}, 'Item');
|
|
335
425
|
item.itemKey = itemKey;
|
|
336
|
-
const trigger = make$
|
|
337
|
-
const question = make$
|
|
426
|
+
const trigger = make$3(create, `${itemKey}-trigger`, Type$4.LinkedContainer, AccordionRole.Trigger, Style$4.Trigger, {}, 'Header');
|
|
427
|
+
const question = make$3(create, `${itemKey}-question`, Type$4.Heading, null, Style$4.Question, { content: words.question, level: DEFAULT_LEVEL }, 'Heading');
|
|
338
428
|
writeStyles(question, QUESTION_BASE);
|
|
339
429
|
trigger.children = [question, ...(icon === AccordionIcon.None ? [] : [buildCompositeIcon(create, `${itemKey}-icon`, icon, item.id)])];
|
|
340
|
-
const panel = make$
|
|
341
|
-
const answer = make$
|
|
430
|
+
const panel = make$3(create, `${itemKey}-panel`, Type$4.Container, AccordionRole.Panel, Style$4.Panel, {}, 'Panel');
|
|
431
|
+
const answer = make$3(create, `${itemKey}-answer`, Type$4.Text, null, Style$4.Answer, { content: words.answer, tag: 'p' }, 'Text');
|
|
342
432
|
writeStyles(answer, ANSWER_BASE);
|
|
343
433
|
panel.children = [answer];
|
|
344
434
|
item.children = [trigger, panel];
|
|
@@ -346,7 +436,7 @@ function buildAccordionItem(create, words, itemKey, icon = AccordionIcon.Chevron
|
|
|
346
436
|
}
|
|
347
437
|
/** A fresh Accordion: three items, one open at a time, the first open by default (board 4). */
|
|
348
438
|
function buildAccordionRecipe(create, options = {}) {
|
|
349
|
-
const root = make$
|
|
439
|
+
const root = make$3(create, 'accordion', Type$4.Container, AccordionRole.Accordion, Style$4.Root, {}, 'Accordion');
|
|
350
440
|
writeProp(root, CompositeBehaviourTrait.Multiple, options.multiple ?? false);
|
|
351
441
|
writeProp(root, CompositeBehaviourTrait.DefaultOn, options.defaultOn ?? AccordionDefaultOn.First);
|
|
352
442
|
const words = options.items ?? ACCORDION_WORDS;
|
|
@@ -385,7 +475,7 @@ function accordionItemOf(node, path) {
|
|
|
385
475
|
}
|
|
386
476
|
/** True when the root takes its items from content (a Repeater bound to a list, R3). */
|
|
387
477
|
function isAccordionFromContent(root) {
|
|
388
|
-
return !!root && root.type === Type$
|
|
478
|
+
return !!root && root.type === Type$4.Repeater;
|
|
389
479
|
}
|
|
390
480
|
/** The root's items in order — every item (Manual), or the one template item (From content). */
|
|
391
481
|
function accordionItems(root) {
|
|
@@ -409,7 +499,8 @@ function setAccordionBehaviour(root, patch) {
|
|
|
409
499
|
}
|
|
410
500
|
/** The roles of the kind an item belongs to (an accordion item, or a disclosure — its own item). */
|
|
411
501
|
function kindRolesOf(node) {
|
|
412
|
-
|
|
502
|
+
const kind = compositeKindOfRole(node.role);
|
|
503
|
+
return COMPOSITE_KIND_ROLES[isToggleKind(kind) ? kind : CompositeKind.Accordion];
|
|
413
504
|
}
|
|
414
505
|
/** The header of an item (either kind). */
|
|
415
506
|
function accordionTriggerOf(item) {
|
|
@@ -431,9 +522,9 @@ function accordionQuestionOf(item) {
|
|
|
431
522
|
let text;
|
|
432
523
|
if (trigger)
|
|
433
524
|
walkComposite(trigger, n => {
|
|
434
|
-
if (!hit && n.type === Type$
|
|
525
|
+
if (!hit && n.type === Type$4.Heading)
|
|
435
526
|
hit = n;
|
|
436
|
-
if (!text && n.type === Type$
|
|
527
|
+
if (!text && n.type === Type$4.Text)
|
|
437
528
|
text = n;
|
|
438
529
|
});
|
|
439
530
|
return hit ?? text;
|
|
@@ -443,15 +534,15 @@ function accordionAnswerOf(item) {
|
|
|
443
534
|
const panel = accordionPanelOf(item);
|
|
444
535
|
let hit;
|
|
445
536
|
if (panel)
|
|
446
|
-
walkComposite(panel, n => { if (!hit && n.type === Type$
|
|
537
|
+
walkComposite(panel, n => { if (!hit && n.type === Type$4.Text)
|
|
447
538
|
hit = n; });
|
|
448
539
|
return hit;
|
|
449
540
|
}
|
|
450
|
-
const stripTags = (v) => String(v ?? '').replace(/<[^>]*>/g, '').trim();
|
|
541
|
+
const stripTags$1 = (v) => String(v ?? '').replace(/<[^>]*>/g, '').trim();
|
|
451
542
|
/** An item's name in the Items list and the layer tree: its question's words. */
|
|
452
543
|
function accordionItemLabel(item) {
|
|
453
544
|
const q = accordionQuestionOf(item);
|
|
454
|
-
return q ? stripTags(readProp(q, 'content')) : '';
|
|
545
|
+
return q ? stripTags$1(readProp(q, 'content')) : '';
|
|
455
546
|
}
|
|
456
547
|
/** Items ▸ rename: the question's words. */
|
|
457
548
|
function renameAccordionItem(item, question) {
|
|
@@ -464,7 +555,7 @@ function accordionHeadingLevel(trigger) {
|
|
|
464
555
|
const visit = (n) => {
|
|
465
556
|
if (level !== undefined)
|
|
466
557
|
return;
|
|
467
|
-
if (n.type === Type$
|
|
558
|
+
if (n.type === Type$4.Heading) {
|
|
468
559
|
level = readProp(n, 'level');
|
|
469
560
|
return;
|
|
470
561
|
}
|
|
@@ -644,6 +735,7 @@ function rowItemKey(row, index) {
|
|
|
644
735
|
const DOM_ID_PREFIX = {
|
|
645
736
|
[CompositeKind.Accordion]: 'iox-acc',
|
|
646
737
|
[CompositeKind.Disclosure]: 'iox-dis',
|
|
738
|
+
[CompositeKind.Tabs]: 'iox-tab',
|
|
647
739
|
};
|
|
648
740
|
/** The ARIA pairing ids of an item: unique per composite (its SCOPE id — `compositeScopeId`) and per item (its key). */
|
|
649
741
|
function accordionDomIds(rootId, itemKey, kind = CompositeKind.Accordion) {
|
|
@@ -731,14 +823,14 @@ function accordionPanelAttributes(state) {
|
|
|
731
823
|
function serializeDomAttributes(attrs, esc) {
|
|
732
824
|
return Object.entries(attrs).map(([k, v]) => (v === '' ? ` ${k}` : ` ${k}="${esc(v)}"`)).join('');
|
|
733
825
|
}
|
|
734
|
-
const SHORT_TEXT = new Set(['text']);
|
|
735
|
-
const LONG_TEXT = new Set(['textarea', 'richText', 'editor']);
|
|
826
|
+
const SHORT_TEXT$1 = new Set(['text']);
|
|
827
|
+
const LONG_TEXT$1 = new Set(['textarea', 'richText', 'editor']);
|
|
736
828
|
/** R3 (board 9 Q19): switching to From content binds the question to the first short-text field and the
|
|
737
829
|
* answer to the first long-text field. ONE rule for the builder's switch and the composer. */
|
|
738
830
|
function pickAccordionAutoBinds(fields) {
|
|
739
831
|
const isString = (f) => !f.type || f.type === 'string';
|
|
740
|
-
const question = fields.find(f => isString(f) && SHORT_TEXT.has(String(f.controller ?? 'text')))?.name ?? null;
|
|
741
|
-
const answer = fields.find(f => isString(f) && LONG_TEXT.has(String(f.controller ?? '')) && f.name !== question)?.name ?? null;
|
|
832
|
+
const question = fields.find(f => isString(f) && SHORT_TEXT$1.has(String(f.controller ?? 'text')))?.name ?? null;
|
|
833
|
+
const answer = fields.find(f => isString(f) && LONG_TEXT$1.has(String(f.controller ?? '')) && f.name !== question)?.name ?? null;
|
|
742
834
|
return { question, answer };
|
|
743
835
|
}
|
|
744
836
|
/** The bound props of the question and the answer (each a content trait). */
|
|
@@ -818,12 +910,12 @@ function accordionPartStyle(node, name) {
|
|
|
818
910
|
*/
|
|
819
911
|
const ITEM_ON_EVENT = 'iox-item-on';
|
|
820
912
|
const ITEM_OFF_EVENT = 'iox-item-off';
|
|
821
|
-
const KIND_ROOT = (kind) => `[${COMPOSITE_ATTRIBUTE}="${kind}"]`;
|
|
822
|
-
const ROOT_SELECTOR = Object.
|
|
913
|
+
const KIND_ROOT$1 = (kind) => `[${COMPOSITE_ATTRIBUTE}="${kind}"]`;
|
|
914
|
+
const ROOT_SELECTOR$1 = Object.keys(COMPOSITE_KIND_ROLES).map(KIND_ROOT$1).join(', ');
|
|
823
915
|
/** An accordion's items are its `.iox-role-accordion-item` children; a disclosure is its own item. */
|
|
824
916
|
const ACCORDION_ITEM_SELECTOR = '.iox-role-accordion-item';
|
|
825
|
-
const ITEM_SELECTOR = `${ACCORDION_ITEM_SELECTOR}, ${KIND_ROOT(CompositeKind.Disclosure)}`;
|
|
826
|
-
const PANEL_SELECTOR = '.iox-role-accordion-panel, .iox-role-disclosure-panel';
|
|
917
|
+
const ITEM_SELECTOR = `${ACCORDION_ITEM_SELECTOR}, ${KIND_ROOT$1(CompositeKind.Disclosure)}`;
|
|
918
|
+
const PANEL_SELECTOR$1 = '.iox-role-accordion-panel, .iox-role-disclosure-panel';
|
|
827
919
|
const BUTTON_SELECTOR = `.${AccordionClass.Button}`;
|
|
828
920
|
const TRACK_SELECTOR = `.${AccordionClass.Track}`;
|
|
829
921
|
const REGION_SELECTOR = `.${AccordionClass.Region}`;
|
|
@@ -832,16 +924,16 @@ const MAX_CLOSE_MS = 600;
|
|
|
832
924
|
const REDUCED_MOTION = '(prefers-reduced-motion: reduce)';
|
|
833
925
|
/** The composite (accordion or disclosure) an element belongs to (the element itself when it is a root). */
|
|
834
926
|
function closestAccordion(el) {
|
|
835
|
-
return el?.closest?.(ROOT_SELECTOR) ?? null;
|
|
927
|
+
return el?.closest?.(ROOT_SELECTOR$1) ?? null;
|
|
836
928
|
}
|
|
837
929
|
/** The item an element sits in (itself when it is one) — an accordion item, or a disclosure. */
|
|
838
930
|
function closestAccordionItem(el) {
|
|
839
931
|
return el?.closest?.(ITEM_SELECTOR) ?? null;
|
|
840
932
|
}
|
|
841
|
-
const kindOf = (root) => root.getAttribute(COMPOSITE_ATTRIBUTE) === CompositeKind.Disclosure ? CompositeKind.Disclosure : CompositeKind.Accordion;
|
|
933
|
+
const kindOf$1 = (root) => root.getAttribute(COMPOSITE_ATTRIBUTE) === CompositeKind.Disclosure ? CompositeKind.Disclosure : CompositeKind.Accordion;
|
|
842
934
|
/** The composite root whose items an item belongs to: a disclosure is its own root. */
|
|
843
|
-
function
|
|
844
|
-
return kindOf(item) === CompositeKind.Disclosure && item.matches(KIND_ROOT(CompositeKind.Disclosure)) ? item : closestAccordion(item.parentElement);
|
|
935
|
+
function accordionRootOfItem(item) {
|
|
936
|
+
return kindOf$1(item) === CompositeKind.Disclosure && item.matches(KIND_ROOT$1(CompositeKind.Disclosure)) ? item : closestAccordion(item.parentElement);
|
|
845
937
|
}
|
|
846
938
|
/** Subscribe to an item turning on / off — what the `itemOn` / `itemOff` interaction triggers listen to. The
|
|
847
939
|
* item is the element's own item (the element itself when it is one). Returns the unsubscribe. */
|
|
@@ -857,20 +949,20 @@ function onAccordionItemState(el, on, handler) {
|
|
|
857
949
|
function isAccordionItemOn(el) {
|
|
858
950
|
return !!closestAccordionItem(el)?.hasAttribute(ON_ATTRIBUTE);
|
|
859
951
|
}
|
|
860
|
-
const nodeIdOf = (el) => {
|
|
952
|
+
const nodeIdOf$1 = (el) => {
|
|
861
953
|
for (const c of Array.from(el.classList))
|
|
862
954
|
if (c.startsWith('iox-node-'))
|
|
863
955
|
return c.slice('iox-node-'.length);
|
|
864
956
|
return '';
|
|
865
957
|
};
|
|
866
|
-
function behaviourOf(root) {
|
|
958
|
+
function behaviourOf$1(root) {
|
|
867
959
|
const raw = root.getAttribute(DEFAULT_ON_ATTRIBUTE);
|
|
868
960
|
return {
|
|
869
961
|
multiple: root.hasAttribute(MULTIPLE_ATTRIBUTE),
|
|
870
962
|
defaultOn: ACCORDION_DEFAULT_ONS.includes(raw) ? raw : AccordionDefaultOn.First,
|
|
871
963
|
};
|
|
872
964
|
}
|
|
873
|
-
function setAttr(el, name, value) {
|
|
965
|
+
function setAttr$1(el, name, value) {
|
|
874
966
|
if (!el)
|
|
875
967
|
return;
|
|
876
968
|
if (value === null) {
|
|
@@ -925,8 +1017,8 @@ class AccordionRuntime {
|
|
|
925
1017
|
return;
|
|
926
1018
|
this.open(this.keyOf(item, items));
|
|
927
1019
|
};
|
|
928
|
-
this.kind = options.kind ?? kindOf(root);
|
|
929
|
-
this.behaviourValue = options.behaviour ?? behaviourOf(root);
|
|
1020
|
+
this.kind = options.kind ?? kindOf$1(root);
|
|
1021
|
+
this.behaviourValue = options.behaviour ?? behaviourOf$1(root);
|
|
930
1022
|
root.setAttribute(COMPOSITE_ATTRIBUTE, this.kind);
|
|
931
1023
|
RUNTIMES.set(root, this);
|
|
932
1024
|
// The server rendered the first state; with no server state the root's default decides.
|
|
@@ -1059,7 +1151,7 @@ class AccordionRuntime {
|
|
|
1059
1151
|
}
|
|
1060
1152
|
const forced = this.options.forced?.() ?? new Set();
|
|
1061
1153
|
const own = this.options.rootId;
|
|
1062
|
-
const rootId = (typeof own === 'function' ? own() : own) ?? this.root.getAttribute(SCOPE_ATTRIBUTE) ?? nodeIdOf(this.root);
|
|
1154
|
+
const rootId = (typeof own === 'function' ? own() : own) ?? this.root.getAttribute(SCOPE_ATTRIBUTE) ?? nodeIdOf$1(this.root);
|
|
1063
1155
|
const next = new Set();
|
|
1064
1156
|
items.forEach((item, index) => {
|
|
1065
1157
|
const key = this.keyOf(item, items);
|
|
@@ -1076,31 +1168,31 @@ class AccordionRuntime {
|
|
|
1076
1168
|
for (const item of items) {
|
|
1077
1169
|
const key = this.keyOf(item, items);
|
|
1078
1170
|
if (next.has(key) && !was.has(key))
|
|
1079
|
-
dispatch(item, ITEM_ON_EVENT);
|
|
1171
|
+
dispatch$1(item, ITEM_ON_EVENT);
|
|
1080
1172
|
else if (!next.has(key) && was.has(key))
|
|
1081
|
-
dispatch(item, ITEM_OFF_EVENT);
|
|
1173
|
+
dispatch$1(item, ITEM_OFF_EVENT);
|
|
1082
1174
|
}
|
|
1083
1175
|
}
|
|
1084
1176
|
stampItem(item, key, index, on, rootId, first) {
|
|
1085
1177
|
const ids = accordionDomIds(rootId, key, this.kind);
|
|
1086
1178
|
if (!item.hasAttribute(ITEM_KEY_ATTRIBUTE))
|
|
1087
1179
|
item.setAttribute(ITEM_KEY_ATTRIBUTE, key);
|
|
1088
|
-
setAttr(item, ON_ATTRIBUTE, on ? '' : null);
|
|
1180
|
+
setAttr$1(item, ON_ATTRIBUTE, on ? '' : null);
|
|
1089
1181
|
const button = this.own(item, BUTTON_SELECTOR);
|
|
1090
1182
|
const track = this.own(item, TRACK_SELECTOR);
|
|
1091
1183
|
const region = this.own(item, REGION_SELECTOR);
|
|
1092
|
-
const panel = this.own(item, PANEL_SELECTOR);
|
|
1093
|
-
setAttr(button, 'type', 'button');
|
|
1094
|
-
setAttr(button, 'id', ids.trigger);
|
|
1095
|
-
setAttr(button, 'aria-expanded', on ? 'true' : 'false');
|
|
1096
|
-
setAttr(button, 'aria-controls', ids.panel);
|
|
1097
|
-
setAttr(button, ON_ATTRIBUTE, on ? '' : null);
|
|
1098
|
-
setAttr(track, ON_ATTRIBUTE, on ? '' : null);
|
|
1099
|
-
setAttr(panel, ON_ATTRIBUTE, on ? '' : null);
|
|
1184
|
+
const panel = this.own(item, PANEL_SELECTOR$1);
|
|
1185
|
+
setAttr$1(button, 'type', 'button');
|
|
1186
|
+
setAttr$1(button, 'id', ids.trigger);
|
|
1187
|
+
setAttr$1(button, 'aria-expanded', on ? 'true' : 'false');
|
|
1188
|
+
setAttr$1(button, 'aria-controls', ids.panel);
|
|
1189
|
+
setAttr$1(button, ON_ATTRIBUTE, on ? '' : null);
|
|
1190
|
+
setAttr$1(track, ON_ATTRIBUTE, on ? '' : null);
|
|
1191
|
+
setAttr$1(panel, ON_ATTRIBUTE, on ? '' : null);
|
|
1100
1192
|
const labelled = hasLabelledRegion(this.kind);
|
|
1101
|
-
setAttr(region, 'role', labelled ? 'region' : null);
|
|
1102
|
-
setAttr(region, 'id', ids.panel);
|
|
1103
|
-
setAttr(region, 'aria-labelledby', labelled ? ids.trigger : null);
|
|
1193
|
+
setAttr$1(region, 'role', labelled ? 'region' : null);
|
|
1194
|
+
setAttr$1(region, 'id', ids.panel);
|
|
1195
|
+
setAttr$1(region, 'aria-labelledby', labelled ? ids.trigger : null);
|
|
1104
1196
|
if (!region)
|
|
1105
1197
|
return;
|
|
1106
1198
|
const pending = this.closing.get(key);
|
|
@@ -1109,7 +1201,7 @@ class AccordionRuntime {
|
|
|
1109
1201
|
clearTimeout(pending);
|
|
1110
1202
|
this.closing.delete(key);
|
|
1111
1203
|
}
|
|
1112
|
-
setAttr(region, 'hidden', null);
|
|
1204
|
+
setAttr$1(region, 'hidden', null);
|
|
1113
1205
|
return;
|
|
1114
1206
|
}
|
|
1115
1207
|
if (region.hasAttribute('hidden') || pending)
|
|
@@ -1117,13 +1209,13 @@ class AccordionRuntime {
|
|
|
1117
1209
|
// Closing: the track collapses first (its transition), THEN the region is hidden — hiding it at once
|
|
1118
1210
|
// would collapse it with no motion. At first paint, or under reduced motion, it is hidden at once.
|
|
1119
1211
|
if (first || this.reduced()) {
|
|
1120
|
-
setAttr(region, 'hidden', UNTIL_FOUND);
|
|
1212
|
+
setAttr$1(region, 'hidden', UNTIL_FOUND);
|
|
1121
1213
|
return;
|
|
1122
1214
|
}
|
|
1123
1215
|
const hide = () => {
|
|
1124
1216
|
this.closing.delete(key);
|
|
1125
1217
|
if (!item.hasAttribute(ON_ATTRIBUTE))
|
|
1126
|
-
setAttr(region, 'hidden', UNTIL_FOUND);
|
|
1218
|
+
setAttr$1(region, 'hidden', UNTIL_FOUND);
|
|
1127
1219
|
};
|
|
1128
1220
|
this.closing.set(key, setTimeout(hide, this.closeDelay(track)));
|
|
1129
1221
|
}
|
|
@@ -1146,7 +1238,7 @@ class AccordionRuntime {
|
|
|
1146
1238
|
return this.items().map(i => this.own(i, BUTTON_SELECTOR)).filter((b) => !!b);
|
|
1147
1239
|
}
|
|
1148
1240
|
}
|
|
1149
|
-
function dispatch(el, type) {
|
|
1241
|
+
function dispatch$1(el, type) {
|
|
1150
1242
|
const Ctor = globalThis.CustomEvent;
|
|
1151
1243
|
if (Ctor)
|
|
1152
1244
|
el.dispatchEvent(new Ctor(type, { bubbles: false }));
|
|
@@ -1161,7 +1253,7 @@ function attachAccordionAt(root, options = {}) {
|
|
|
1161
1253
|
/** Attach the runtime to every accordion under `container` that has none yet. Returns the new runtimes. */
|
|
1162
1254
|
function attachAccordions(container, options = {}) {
|
|
1163
1255
|
const out = [];
|
|
1164
|
-
container.querySelectorAll(ROOT_SELECTOR).forEach(root => {
|
|
1256
|
+
container.querySelectorAll(ROOT_SELECTOR$1).forEach(root => {
|
|
1165
1257
|
const runtime = attachAccordionAt(root, options);
|
|
1166
1258
|
if (runtime)
|
|
1167
1259
|
out.push(runtime);
|
|
@@ -1198,18 +1290,6 @@ var CompositeItemAction;
|
|
|
1198
1290
|
CompositeItemAction["Close"] = "close";
|
|
1199
1291
|
CompositeItemAction["Toggle"] = "toggle";
|
|
1200
1292
|
})(CompositeItemAction || (CompositeItemAction = {}));
|
|
1201
|
-
/**
|
|
1202
|
-
* Run an item action on the element of an ITEM (an accordion item, or a disclosure) — the `openItem` /
|
|
1203
|
-
* `closeItem` / `toggleItem` interaction actions, on the canvas and on the live site through this one function.
|
|
1204
|
-
* Returns whether the item shows open afterwards, or null when `el` is in no running composite.
|
|
1205
|
-
*/
|
|
1206
|
-
function runCompositeItemAction(el, action) {
|
|
1207
|
-
const item = closestAccordionItem(el);
|
|
1208
|
-
const runtime = item ? accordionRuntimeOf(rootOfItem(item)) : null;
|
|
1209
|
-
if (!item || !runtime)
|
|
1210
|
-
return null;
|
|
1211
|
-
return runtime.run(action, runtime.keyOf(item));
|
|
1212
|
-
}
|
|
1213
1293
|
/** How much of an opened item must already be on screen for the action NOT to scroll to it (its header row). */
|
|
1214
1294
|
const VISIBLE_HEAD_PX = 48;
|
|
1215
1295
|
/** True when an item an action just opened is out of view and the page should scroll to it (T14: "opens that item
|
|
@@ -1226,21 +1306,21 @@ const DISCLOSURE_WORDS = {
|
|
|
1226
1306
|
};
|
|
1227
1307
|
/** A disclosure's one item key — its root IS its item. */
|
|
1228
1308
|
const DISCLOSURE_ITEM_KEY = 'k1';
|
|
1229
|
-
var Type$
|
|
1309
|
+
var Type$3;
|
|
1230
1310
|
(function (Type) {
|
|
1231
1311
|
Type["Container"] = "Container";
|
|
1232
1312
|
Type["LinkedContainer"] = "LinkedContainer";
|
|
1233
1313
|
Type["Text"] = "Text";
|
|
1234
|
-
})(Type$
|
|
1235
|
-
var Style$
|
|
1314
|
+
})(Type$3 || (Type$3 = {}));
|
|
1315
|
+
var Style$3;
|
|
1236
1316
|
(function (Style) {
|
|
1237
1317
|
Style["Root"] = "box";
|
|
1238
1318
|
Style["Trigger"] = "accordion-trigger";
|
|
1239
1319
|
Style["Word"] = "body";
|
|
1240
1320
|
Style["Panel"] = "accordion-panel";
|
|
1241
1321
|
Style["Body"] = "body";
|
|
1242
|
-
})(Style$
|
|
1243
|
-
const ZERO_BOX$
|
|
1322
|
+
})(Style$3 || (Style$3 = {}));
|
|
1323
|
+
const ZERO_BOX$2 = '0px 0px 0px 0px';
|
|
1244
1324
|
const HIDDEN = 'none';
|
|
1245
1325
|
const INHERIT = 'inherit';
|
|
1246
1326
|
/** A word is a flex item of the toggle — `block` (never `inline`: the compiler drops a value equal to the CSS initial,
|
|
@@ -1249,7 +1329,7 @@ const SHOWN = 'block';
|
|
|
1249
1329
|
/** What each part holds — layout only; its LOOK is its element style. The trigger sizes to its words (a button in
|
|
1250
1330
|
* the flow, not a full-width header row — `fit-content` keeps it from stretching) and the panel spans the root. The
|
|
1251
1331
|
* trigger is `flex`, not `inline-flex`: it is a flex item of the root, which blockifies it on the site anyway. */
|
|
1252
|
-
const PART_BASE = {
|
|
1332
|
+
const PART_BASE$1 = {
|
|
1253
1333
|
[DisclosureRole.Disclosure]: { display: 'flex', flexDirection: 'column', width: '100%', minHeight: 'auto', height: 'auto' },
|
|
1254
1334
|
// The toggle reads the text around it (the page's body type, through the root) — a LinkedContainer is born with
|
|
1255
1335
|
// its own literal font, which an accordion's heading masks and a disclosure's words would show.
|
|
@@ -1259,17 +1339,17 @@ const PART_BASE = {
|
|
|
1259
1339
|
},
|
|
1260
1340
|
// A word is just its words: none of a Text's own box (its type's 10px padding, its full width) — the toggle's
|
|
1261
1341
|
// padding is the click target.
|
|
1262
|
-
[DisclosureRole.More]: { display: SHOWN, margin: ZERO_BOX$
|
|
1263
|
-
[DisclosureRole.Less]: { display: HIDDEN, margin: ZERO_BOX$
|
|
1342
|
+
[DisclosureRole.More]: { display: SHOWN, margin: ZERO_BOX$2, padding: ZERO_BOX$2, width: 'auto' },
|
|
1343
|
+
[DisclosureRole.Less]: { display: HIDDEN, margin: ZERO_BOX$2, padding: ZERO_BOX$2, width: 'auto' },
|
|
1264
1344
|
[DisclosureRole.Panel]: { display: 'block', width: '100%', minHeight: 'auto', height: 'auto' },
|
|
1265
1345
|
};
|
|
1266
|
-
const BODY_BASE = { margin: ZERO_BOX$
|
|
1267
|
-
function make$
|
|
1268
|
-
return makeCompositePart(create, { key, type, props, elementStyle: style }, role, label, role ? PART_BASE[role] : undefined);
|
|
1346
|
+
const BODY_BASE$1 = { margin: ZERO_BOX$2, padding: ZERO_BOX$2, minHeight: 'auto' };
|
|
1347
|
+
function make$2(create, key, type, role, style, props, label) {
|
|
1348
|
+
return makeCompositePart(create, { key, type, props, elementStyle: style }, role, label, role ? PART_BASE$1[role] : undefined);
|
|
1269
1349
|
}
|
|
1270
1350
|
/** One of the trigger's two words: shown while the disclosure is closed (`more`) or open (`less`). */
|
|
1271
1351
|
function buildWord(create, role, words, rootId) {
|
|
1272
|
-
const node = make$
|
|
1352
|
+
const node = make$2(create, role === DisclosureRole.More ? 'more' : 'less', Type$3.Text, role, Style$3.Word, { content: words, tag: 'span' }, 'Text');
|
|
1273
1353
|
node.stateStyles = { ...(node.stateStyles ?? {}), [ITEM_OPEN_STATE]: { display: role === DisclosureRole.More ? HIDDEN : SHOWN } };
|
|
1274
1354
|
if (rootId)
|
|
1275
1355
|
node.stateTriggers = { ...(node.stateTriggers ?? {}), [ITEM_OPEN_STATE]: rootId };
|
|
@@ -1278,19 +1358,19 @@ function buildWord(create, role, words, rootId) {
|
|
|
1278
1358
|
/** A fresh Disclosure: "Read more" opens the text below and reads "Read less" while it is open; closed by default. */
|
|
1279
1359
|
function buildDisclosureRecipe(create, options = {}) {
|
|
1280
1360
|
const words = { ...DISCLOSURE_WORDS, ...(options.words ?? {}) };
|
|
1281
|
-
const root = make$
|
|
1361
|
+
const root = make$2(create, 'disclosure', Type$3.Container, DisclosureRole.Disclosure, Style$3.Root, {}, 'Disclosure');
|
|
1282
1362
|
root.itemKey = DISCLOSURE_ITEM_KEY;
|
|
1283
1363
|
writeProp(root, CompositeBehaviourTrait.DefaultOn, options.open ? AccordionDefaultOn.First : AccordionDefaultOn.None);
|
|
1284
1364
|
const icon = options.icon ?? AccordionIcon.Chevron;
|
|
1285
|
-
const trigger = make$
|
|
1365
|
+
const trigger = make$2(create, 'trigger', Type$3.LinkedContainer, DisclosureRole.Trigger, Style$3.Trigger, {}, 'Toggle');
|
|
1286
1366
|
trigger.children = [
|
|
1287
1367
|
buildWord(create, DisclosureRole.More, words.more, root.id),
|
|
1288
1368
|
buildWord(create, DisclosureRole.Less, words.less, root.id),
|
|
1289
1369
|
...(icon === AccordionIcon.None ? [] : [buildCompositeIcon(create, 'icon', icon, root.id, DisclosureRole.Icon)]),
|
|
1290
1370
|
];
|
|
1291
|
-
const panel = make$
|
|
1292
|
-
const body = make$
|
|
1293
|
-
writeStyles(body, BODY_BASE);
|
|
1371
|
+
const panel = make$2(create, 'panel', Type$3.Container, DisclosureRole.Panel, Style$3.Panel, {}, 'Panel');
|
|
1372
|
+
const body = make$2(create, 'body', Type$3.Text, null, Style$3.Body, { content: words.body, tag: 'p' }, 'Text');
|
|
1373
|
+
writeStyles(body, BODY_BASE$1);
|
|
1294
1374
|
panel.children = [body];
|
|
1295
1375
|
root.children = [trigger, panel];
|
|
1296
1376
|
return root;
|
|
@@ -1452,7 +1532,7 @@ const CARD_WORDS = {
|
|
|
1452
1532
|
};
|
|
1453
1533
|
/** The breakpoint whose layer stacks an "Image side" card (Breakpoint.Mobile, ≤767px). */
|
|
1454
1534
|
const CARD_STACK_BREAKPOINT = 'mobile';
|
|
1455
|
-
var Type$
|
|
1535
|
+
var Type$2;
|
|
1456
1536
|
(function (Type) {
|
|
1457
1537
|
Type["Container"] = "Container";
|
|
1458
1538
|
Type["Image"] = "Image";
|
|
@@ -1460,8 +1540,8 @@ var Type$1;
|
|
|
1460
1540
|
Type["Text"] = "Text";
|
|
1461
1541
|
Type["Icon"] = "Icon";
|
|
1462
1542
|
Type["ButtonBlock"] = "ButtonBlock";
|
|
1463
|
-
})(Type$
|
|
1464
|
-
var Style$
|
|
1543
|
+
})(Type$2 || (Type$2 = {}));
|
|
1544
|
+
var Style$2;
|
|
1465
1545
|
(function (Style) {
|
|
1466
1546
|
Style["Card"] = "card";
|
|
1467
1547
|
Style["Box"] = "box";
|
|
@@ -1474,7 +1554,7 @@ var Style$1;
|
|
|
1474
1554
|
Style["Ghost"] = "button-ghost";
|
|
1475
1555
|
Style["Icon"] = "icon";
|
|
1476
1556
|
Style["Scrim"] = "media-scrim";
|
|
1477
|
-
})(Style$
|
|
1557
|
+
})(Style$2 || (Style$2 = {}));
|
|
1478
1558
|
var Scheme;
|
|
1479
1559
|
(function (Scheme) {
|
|
1480
1560
|
Scheme["Default"] = "default";
|
|
@@ -1484,11 +1564,11 @@ const LINK_TYPE = 'linkType';
|
|
|
1484
1564
|
const SAVE_ICON = 'ph ph-bookmark-simple';
|
|
1485
1565
|
/** A value an element style writes — read from the contract, never retyped. */
|
|
1486
1566
|
const styleValue = (style, trait) => String(elementStyleById(style)?.base[trait] ?? '');
|
|
1487
|
-
const CARD_PAD = styleValue(Style$
|
|
1488
|
-
const IMAGE_RADIUS = styleValue(Style$
|
|
1489
|
-
const TITLE_SIZE = styleValue(Style$
|
|
1567
|
+
const CARD_PAD = styleValue(Style$2.Card, 'padding');
|
|
1568
|
+
const IMAGE_RADIUS = styleValue(Style$2.Media, 'borderRadius');
|
|
1569
|
+
const TITLE_SIZE = styleValue(Style$2.Title, 'fontSize');
|
|
1490
1570
|
const LEAD_SIZE = 'var(--iox-size-lead)';
|
|
1491
|
-
const ZERO_BOX = '0px 0px 0px 0px';
|
|
1571
|
+
const ZERO_BOX$1 = '0px 0px 0px 0px';
|
|
1492
1572
|
/** What every card part holds in every layout — the recipe's box. */
|
|
1493
1573
|
const CARD_BASE = {
|
|
1494
1574
|
// No height: a card is a grid or flex item, and the cell's stretch is what makes a row's cards equal.
|
|
@@ -1496,25 +1576,25 @@ const CARD_BASE = {
|
|
|
1496
1576
|
[CardRole.Card]: { display: 'flex', gap: '14px', width: '100%', minWidth: '0px', overflow: 'hidden', position: 'relative' },
|
|
1497
1577
|
[CardRole.Media]: { display: 'block', width: '100%', minHeight: 'auto', flexGrow: '0', flexShrink: '0' },
|
|
1498
1578
|
[CardRole.Content]: { display: 'flex', flexDirection: 'column', gap: '8px', width: '100%', minWidth: '0px', minHeight: 'auto' },
|
|
1499
|
-
[CardRole.Eyebrow]: { margin: ZERO_BOX, padding: ZERO_BOX, minHeight: 'auto' },
|
|
1500
|
-
[CardRole.Title]: { margin: ZERO_BOX },
|
|
1501
|
-
[CardRole.Text]: { margin: ZERO_BOX, padding: ZERO_BOX, minHeight: 'auto' },
|
|
1579
|
+
[CardRole.Eyebrow]: { margin: ZERO_BOX$1, padding: ZERO_BOX$1, minHeight: 'auto' },
|
|
1580
|
+
[CardRole.Title]: { margin: ZERO_BOX$1 },
|
|
1581
|
+
[CardRole.Text]: { margin: ZERO_BOX$1, padding: ZERO_BOX$1, minHeight: 'auto' },
|
|
1502
1582
|
// Buttons pinned to the bottom (board 8 Q: equal heights + buttons pinned): the content column grows,
|
|
1503
1583
|
// and the row's auto top margin takes the slack.
|
|
1504
1584
|
[CardRole.Actions]: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: '8px', margin: 'auto 0px 0px 0px', padding: '6px 0px 0px 0px', width: '100%', minHeight: 'auto' },
|
|
1505
1585
|
};
|
|
1506
|
-
const IMAGE_BASE$
|
|
1586
|
+
const IMAGE_BASE$2 = { display: 'block', width: '100%', maxWidth: '100%' };
|
|
1507
1587
|
/** A button's label and a pill's words: inherit the type and ink of what holds them. */
|
|
1508
|
-
const LABEL_BASE = { width: 'auto', minHeight: 'auto', padding: ZERO_BOX, margin: ZERO_BOX };
|
|
1588
|
+
const LABEL_BASE$1 = { width: 'auto', minHeight: 'auto', padding: ZERO_BOX$1, margin: ZERO_BOX$1 };
|
|
1509
1589
|
const INLINE_ICON = { fontSize: '1em' };
|
|
1510
1590
|
const IN_FLOW = { position: 'relative', top: 'auto', right: 'auto', bottom: 'auto', left: 'auto' };
|
|
1511
|
-
const GROWING_CONTENT = { position: 'static', flexGrow: '1', flexShrink: '1', flexBasis: '0%', padding: ZERO_BOX };
|
|
1591
|
+
const GROWING_CONTENT = { position: 'static', flexGrow: '1', flexShrink: '1', flexBasis: '0%', padding: ZERO_BOX$1 };
|
|
1512
1592
|
const LAYOUTS = {
|
|
1513
1593
|
[CardLayout.Top]: {
|
|
1514
1594
|
card: { flexDirection: 'column', alignItems: 'stretch', justifyContent: 'flex-start', minHeight: 'auto', padding: CARD_PAD },
|
|
1515
1595
|
media: { ...IN_FLOW, flexBasis: 'auto' },
|
|
1516
1596
|
image: { aspectRatio: '4 / 3', height: 'auto', minHeight: 'auto', borderRadius: IMAGE_RADIUS },
|
|
1517
|
-
content: GROWING_CONTENT, contentStyle: Style$
|
|
1597
|
+
content: GROWING_CONTENT, contentStyle: Style$2.Box, contentScheme: Scheme.Default,
|
|
1518
1598
|
title: { fontSize: TITLE_SIZE },
|
|
1519
1599
|
badgeOnMedia: true,
|
|
1520
1600
|
},
|
|
@@ -1522,7 +1602,7 @@ const LAYOUTS = {
|
|
|
1522
1602
|
card: { flexDirection: 'row', alignItems: 'stretch', justifyContent: 'flex-start', minHeight: 'auto', padding: CARD_PAD },
|
|
1523
1603
|
media: { ...IN_FLOW, flexBasis: '42%' },
|
|
1524
1604
|
image: { aspectRatio: 'auto', height: '100%', minHeight: '180px', borderRadius: IMAGE_RADIUS },
|
|
1525
|
-
content: GROWING_CONTENT, contentStyle: Style$
|
|
1605
|
+
content: GROWING_CONTENT, contentStyle: Style$2.Box, contentScheme: Scheme.Default,
|
|
1526
1606
|
title: { fontSize: TITLE_SIZE },
|
|
1527
1607
|
badgeOnMedia: true,
|
|
1528
1608
|
stack: {
|
|
@@ -1532,11 +1612,11 @@ const LAYOUTS = {
|
|
|
1532
1612
|
},
|
|
1533
1613
|
},
|
|
1534
1614
|
[CardLayout.Overlay]: {
|
|
1535
|
-
card: { flexDirection: 'column', alignItems: 'stretch', justifyContent: 'flex-end', minHeight: '340px', padding: ZERO_BOX },
|
|
1615
|
+
card: { flexDirection: 'column', alignItems: 'stretch', justifyContent: 'flex-end', minHeight: '340px', padding: ZERO_BOX$1 },
|
|
1536
1616
|
media: { position: 'absolute', top: '0px', right: '0px', bottom: '0px', left: '0px', flexBasis: 'auto' },
|
|
1537
1617
|
image: { aspectRatio: 'auto', height: '100%', minHeight: 'auto', borderRadius: '0px' },
|
|
1538
1618
|
content: { position: 'relative', flexGrow: '0', flexShrink: '0', flexBasis: 'auto', padding: `72px ${CARD_PAD} ${CARD_PAD} ${CARD_PAD}` },
|
|
1539
|
-
contentStyle: Style$
|
|
1619
|
+
contentStyle: Style$2.Scrim, contentScheme: Scheme.Inverse,
|
|
1540
1620
|
title: { fontSize: TITLE_SIZE },
|
|
1541
1621
|
badgeOnMedia: false,
|
|
1542
1622
|
},
|
|
@@ -1544,7 +1624,7 @@ const LAYOUTS = {
|
|
|
1544
1624
|
card: { flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-start', minHeight: 'auto', padding: CARD_PAD },
|
|
1545
1625
|
media: { ...IN_FLOW, flexBasis: '72px' },
|
|
1546
1626
|
image: { aspectRatio: '1 / 1', height: 'auto', minHeight: 'auto', borderRadius: IMAGE_RADIUS },
|
|
1547
|
-
content: GROWING_CONTENT, contentStyle: Style$
|
|
1627
|
+
content: GROWING_CONTENT, contentStyle: Style$2.Box, contentScheme: Scheme.Default,
|
|
1548
1628
|
title: { fontSize: LEAD_SIZE },
|
|
1549
1629
|
badgeOnMedia: false,
|
|
1550
1630
|
partsOff: [CardPart.Text, CardPart.Actions],
|
|
@@ -1555,7 +1635,7 @@ const STACK_KEYS = {
|
|
|
1555
1635
|
media: Object.keys(LAYOUTS[CardLayout.Side].stack.media),
|
|
1556
1636
|
image: Object.keys(LAYOUTS[CardLayout.Side].stack.image),
|
|
1557
1637
|
};
|
|
1558
|
-
function make(create, key, type, role, elementStyle, props = {}, label) {
|
|
1638
|
+
function make$1(create, key, type, role, elementStyle, props = {}, label) {
|
|
1559
1639
|
const node = create({ key, type, props, elementStyle });
|
|
1560
1640
|
if (role)
|
|
1561
1641
|
node.role = role;
|
|
@@ -1565,16 +1645,16 @@ function make(create, key, type, role, elementStyle, props = {}, label) {
|
|
|
1565
1645
|
return node;
|
|
1566
1646
|
}
|
|
1567
1647
|
function buttonLabel(create, key, words) {
|
|
1568
|
-
const label = make(create, key, Type$
|
|
1569
|
-
writeStyles(label, LABEL_BASE);
|
|
1648
|
+
const label = make$1(create, key, Type$2.Text, null, Style$2.Label, { content: words, tag: 'span' });
|
|
1649
|
+
writeStyles(label, LABEL_BASE$1);
|
|
1570
1650
|
return label;
|
|
1571
1651
|
}
|
|
1572
1652
|
/** Each optional part (and the locked title) as the recipe builds it. */
|
|
1573
1653
|
const PART_BUILDERS = {
|
|
1574
1654
|
[CardPart.Media]: (create) => {
|
|
1575
|
-
const media = make(create, 'media', Type$
|
|
1576
|
-
const image = make(create, 'image', Type$
|
|
1577
|
-
writeStyles(image, IMAGE_BASE$
|
|
1655
|
+
const media = make$1(create, 'media', Type$2.Container, CardRole.Media, Style$2.Box, {}, 'Media');
|
|
1656
|
+
const image = make$1(create, 'image', Type$2.Image, null, Style$2.Media, { src: '', alt: '' });
|
|
1657
|
+
writeStyles(image, IMAGE_BASE$2);
|
|
1578
1658
|
media.children = [image];
|
|
1579
1659
|
return media;
|
|
1580
1660
|
},
|
|
@@ -1584,14 +1664,14 @@ const PART_BUILDERS = {
|
|
|
1584
1664
|
badge.label = 'Badge';
|
|
1585
1665
|
return badge;
|
|
1586
1666
|
},
|
|
1587
|
-
[CardPart.Eyebrow]: (create, words) => make(create, 'eyebrow', Type$
|
|
1588
|
-
[CardPart.Text]: (create, words) => make(create, 'text', Type$
|
|
1667
|
+
[CardPart.Eyebrow]: (create, words) => make$1(create, 'eyebrow', Type$2.Text, CardRole.Eyebrow, Style$2.Eyebrow, { content: words.eyebrow, tag: 'p' }, 'Eyebrow'),
|
|
1668
|
+
[CardPart.Text]: (create, words) => make$1(create, 'text', Type$2.Text, CardRole.Text, Style$2.Body, { content: words.text, tag: 'p' }, 'Text'),
|
|
1589
1669
|
[CardPart.Actions]: (create, words) => {
|
|
1590
|
-
const actions = make(create, 'actions', Type$
|
|
1591
|
-
const primary = make(create, 'primary', Type$
|
|
1670
|
+
const actions = make$1(create, 'actions', Type$2.Container, CardRole.Actions, Style$2.Box, {}, 'Buttons');
|
|
1671
|
+
const primary = make$1(create, 'primary', Type$2.ButtonBlock, null, Style$2.Primary);
|
|
1592
1672
|
primary.children = [buttonLabel(create, 'primary-label', words.primary)];
|
|
1593
|
-
const ghost = make(create, 'secondary', Type$
|
|
1594
|
-
const icon = make(create, 'secondary-icon', Type$
|
|
1673
|
+
const ghost = make$1(create, 'secondary', Type$2.ButtonBlock, null, Style$2.Ghost);
|
|
1674
|
+
const icon = make$1(create, 'secondary-icon', Type$2.Icon, null, Style$2.Icon, { iconClass: SAVE_ICON });
|
|
1595
1675
|
writeStyles(icon, INLINE_ICON);
|
|
1596
1676
|
ghost.children = [icon, buttonLabel(create, 'secondary-label', words.secondary)];
|
|
1597
1677
|
actions.children = [primary, ghost];
|
|
@@ -1599,15 +1679,15 @@ const PART_BUILDERS = {
|
|
|
1599
1679
|
},
|
|
1600
1680
|
};
|
|
1601
1681
|
function buildTitle(create, words) {
|
|
1602
|
-
return make(create, 'title', Type$
|
|
1682
|
+
return make$1(create, 'title', Type$2.Heading, CardRole.Title, Style$2.Title, { content: words.title, level: 'h3', [LINK_TYPE]: LinkType.None }, 'Title');
|
|
1603
1683
|
}
|
|
1604
1684
|
/** A fresh Card v2: every part a node with its role and element style, laid out by `options.layout`. */
|
|
1605
1685
|
function buildCardRecipe(create, options = {}) {
|
|
1606
1686
|
const words = { ...CARD_WORDS, ...(options.words ?? {}) };
|
|
1607
1687
|
const tone = options.badgeTone ?? BadgeTone.Accent;
|
|
1608
1688
|
const on = (part) => options.parts?.[part] !== false;
|
|
1609
|
-
const card = make(create, 'card', Type$
|
|
1610
|
-
const content = make(create, 'content', Type$
|
|
1689
|
+
const card = make$1(create, 'card', Type$2.Container, CardRole.Card, Style$2.Card, {}, 'Card');
|
|
1690
|
+
const content = make$1(create, 'content', Type$2.Container, CardRole.Content, Style$2.Box, {}, 'Content');
|
|
1611
1691
|
content.children = [
|
|
1612
1692
|
...(on(CardPart.Eyebrow) ? [PART_BUILDERS[CardPart.Eyebrow](create, words, tone)] : []),
|
|
1613
1693
|
buildTitle(create, words),
|
|
@@ -1638,7 +1718,7 @@ function cardRootOf(node, path) {
|
|
|
1638
1718
|
function cardPartsOf(card) {
|
|
1639
1719
|
return Object.fromEntries(CARD_PARTS.map(part => [part, !!findByRole(card, CARD_PART_ROLE[part])]));
|
|
1640
1720
|
}
|
|
1641
|
-
const imageOf = (media) => media?.children?.find(c => c.type === Type$
|
|
1721
|
+
const imageOf = (media) => media?.children?.find(c => c.type === Type$2.Image);
|
|
1642
1722
|
/** Put the badge where the layout says: the Media's top corner, or the head of the content column
|
|
1643
1723
|
* (Image behind text, Compact, or no Media at all). The corner's insets are logical, in iox-design.css
|
|
1644
1724
|
* (mirrored in RTL); `position` is the node's own, because the editing canvas forces every node's
|
|
@@ -1670,7 +1750,7 @@ function applyPartLayout(card, spec, part, baseline) {
|
|
|
1670
1750
|
setStack(part, spec.stack?.media, STACK_KEYS.media);
|
|
1671
1751
|
const image = imageOf(part);
|
|
1672
1752
|
if (image) {
|
|
1673
|
-
writeStyles(image, { ...IMAGE_BASE$
|
|
1753
|
+
writeStyles(image, { ...IMAGE_BASE$2, ...spec.image });
|
|
1674
1754
|
setStack(image, spec.stack?.image, STACK_KEYS.image);
|
|
1675
1755
|
}
|
|
1676
1756
|
return;
|
|
@@ -1921,17 +2001,17 @@ function sliderSlides(children) {
|
|
|
1921
2001
|
return (children ?? []).filter(c => !isSliderPart(c));
|
|
1922
2002
|
}
|
|
1923
2003
|
// ── the parts' recipe ───────────────────────────────────────────────────────────────────────────────
|
|
1924
|
-
var Type;
|
|
2004
|
+
var Type$1;
|
|
1925
2005
|
(function (Type) {
|
|
1926
2006
|
Type["ButtonBlock"] = "ButtonBlock";
|
|
1927
2007
|
Type["Icon"] = "Icon";
|
|
1928
2008
|
Type["Container"] = "Container";
|
|
1929
|
-
})(Type || (Type = {}));
|
|
1930
|
-
var Style;
|
|
2009
|
+
})(Type$1 || (Type$1 = {}));
|
|
2010
|
+
var Style$1;
|
|
1931
2011
|
(function (Style) {
|
|
1932
2012
|
Style["Control"] = "button-secondary";
|
|
1933
2013
|
Style["Icon"] = "icon";
|
|
1934
|
-
})(Style || (Style = {}));
|
|
2014
|
+
})(Style$1 || (Style$1 = {}));
|
|
1935
2015
|
/** Each part's glyph (Phosphor) and its default accessible name. */
|
|
1936
2016
|
const SLIDER_PART_GLYPH = {
|
|
1937
2017
|
[SliderRole.Pause]: 'ph ph-pause',
|
|
@@ -1973,7 +2053,7 @@ function legacyLookOf(slider) {
|
|
|
1973
2053
|
/** One part, from the recipe (board 6: `button-secondary` controls, token dots) or in a migrated slider's legacy look. */
|
|
1974
2054
|
function buildSliderPart(create, role, legacy) {
|
|
1975
2055
|
if (role === SliderRole.Pagination) {
|
|
1976
|
-
const dots = makeCompositePart(create, { key: 'pagination', type: Type.Container, props: {}, elementStyle: NO_ELEMENT_STYLE }, role, PART_LABEL[role], legacy?.paginationColor ? { ...PAGINATION_BASE, color: legacy.paginationColor } : PAGINATION_BASE);
|
|
2056
|
+
const dots = makeCompositePart(create, { key: 'pagination', type: Type$1.Container, props: {}, elementStyle: NO_ELEMENT_STYLE }, role, PART_LABEL[role], legacy?.paginationColor ? { ...PAGINATION_BASE, color: legacy.paginationColor } : PAGINATION_BASE);
|
|
1977
2057
|
dots.children = [];
|
|
1978
2058
|
return dots;
|
|
1979
2059
|
}
|
|
@@ -1989,9 +2069,9 @@ function buildSliderPart(create, role, legacy) {
|
|
|
1989
2069
|
const props = { tag: 'button', type: 'button' };
|
|
1990
2070
|
if (role !== SliderRole.Pause)
|
|
1991
2071
|
props[SLIDER_NAME_TRAIT] = role === SliderRole.Prev ? SLIDER_WORDS.en.prev : SLIDER_WORDS.en.next;
|
|
1992
|
-
const button = makeCompositePart(create, { key, type: Type.ButtonBlock, props, elementStyle: legacy ? NO_ELEMENT_STYLE : Style.Control }, role, PART_LABEL[role], base);
|
|
2072
|
+
const button = makeCompositePart(create, { key, type: Type$1.ButtonBlock, props, elementStyle: legacy ? NO_ELEMENT_STYLE : Style$1.Control }, role, PART_LABEL[role], base);
|
|
1993
2073
|
const iconBase = legacy ? { ...ICON_BASE, fontSize: `${Math.round((legacy.navigationSize ?? 44) * 0.6)}px` } : ICON_BASE;
|
|
1994
|
-
const icon = makeCompositePart(create, { key: `${key}-icon`, type: Type.Icon, props: { iconClass: SLIDER_PART_GLYPH[role] }, elementStyle: legacy ? NO_ELEMENT_STYLE : Style.Icon }, null, 'Icon', iconBase);
|
|
2074
|
+
const icon = makeCompositePart(create, { key: `${key}-icon`, type: Type$1.Icon, props: { iconClass: SLIDER_PART_GLYPH[role] }, elementStyle: legacy ? NO_ELEMENT_STYLE : Style$1.Icon }, null, 'Icon', iconBase);
|
|
1995
2075
|
button.children = [icon];
|
|
1996
2076
|
return button;
|
|
1997
2077
|
}
|
|
@@ -2175,6 +2255,14 @@ const COMPOSITE_ROLE_SPECS = {
|
|
|
2175
2255
|
[DisclosureRole.More]: { parent: DisclosureRole.Trigger },
|
|
2176
2256
|
[DisclosureRole.Less]: { parent: DisclosureRole.Trigger },
|
|
2177
2257
|
[DisclosureRole.Icon]: { parent: DisclosureRole.Trigger },
|
|
2258
|
+
// CC4a — a Tabs always keeps its tab list and its panels holder; a tab lives in the list and a panel in the
|
|
2259
|
+
// holder (each may be deleted — with its pair — but never moved out); the list takes only tabs, the holder only
|
|
2260
|
+
// panels, and a panel takes anything.
|
|
2261
|
+
[TabsRole.Tabs]: { parent: null },
|
|
2262
|
+
[TabsRole.List]: { parent: TabsRole.Tabs, locked: true, accepts: [TabsRole.Trigger] },
|
|
2263
|
+
[TabsRole.Panels]: { parent: TabsRole.Tabs, locked: true, accepts: [TabsRole.Panel] },
|
|
2264
|
+
[TabsRole.Trigger]: { parent: TabsRole.List, pinned: true },
|
|
2265
|
+
[TabsRole.Panel]: { parent: TabsRole.Panels, pinned: true },
|
|
2178
2266
|
// CC3 — a Slider's controls are its own direct children (Swiper is handed them). The pause control stays while
|
|
2179
2267
|
// Autoplay is on (WCAG 2.2.2): the Autoplay switch removes it, never Delete. The arrows and the dots follow
|
|
2180
2268
|
// their switches and may be deleted.
|
|
@@ -2195,6 +2283,8 @@ var StructureRefusal;
|
|
|
2195
2283
|
StructureRefusal["LockedPart"] = "locked-part";
|
|
2196
2284
|
/** A locked part can only live directly inside its own parent role. */
|
|
2197
2285
|
StructureRefusal["LockedPlacement"] = "locked-placement";
|
|
2286
|
+
/** The new parent takes only certain roles (a tab list takes only tabs, CC4a). */
|
|
2287
|
+
StructureRefusal["NotAccepted"] = "not-accepted";
|
|
2198
2288
|
})(StructureRefusal || (StructureRefusal = {}));
|
|
2199
2289
|
/**
|
|
2200
2290
|
* Why the structure guard refuses `edit` on `node`, or null when it is allowed. `ancestors` (Move only) =
|
|
@@ -2202,11 +2292,13 @@ var StructureRefusal;
|
|
|
2202
2292
|
*/
|
|
2203
2293
|
function structureRefusal(node, edit, ancestors = []) {
|
|
2204
2294
|
const spec = node?.role ? COMPOSITE_ROLE_SPECS[node.role] : undefined;
|
|
2295
|
+
const parent = ancestors[ancestors.length - 1];
|
|
2296
|
+
if (edit === StructureEdit.Move && acceptsRefusal(node, parent))
|
|
2297
|
+
return StructureRefusal.NotAccepted;
|
|
2205
2298
|
if (!spec?.locked && !spec?.pinned)
|
|
2206
2299
|
return null;
|
|
2207
2300
|
if (edit === StructureEdit.Delete)
|
|
2208
2301
|
return spec.locked ? StructureRefusal.LockedPart : null;
|
|
2209
|
-
const parent = ancestors[ancestors.length - 1];
|
|
2210
2302
|
const placed = spec.parentType ? parent?.type === spec.parentType : parent?.role === spec.parent;
|
|
2211
2303
|
return placed ? null : StructureRefusal.LockedPlacement;
|
|
2212
2304
|
}
|
|
@@ -2215,6 +2307,13 @@ function structureRefusal(node, edit, ancestors = []) {
|
|
|
2215
2307
|
function isLockedPart(node) {
|
|
2216
2308
|
return !!(node?.role && COMPOSITE_ROLE_SPECS[node.role]?.locked);
|
|
2217
2309
|
}
|
|
2310
|
+
/** Why `parent` refuses `node` as a child (it takes only certain roles), or null. */
|
|
2311
|
+
function acceptsRefusal(node, parent) {
|
|
2312
|
+
const accepts = parent?.role ? COMPOSITE_ROLE_SPECS[parent.role]?.accepts : undefined;
|
|
2313
|
+
if (!accepts)
|
|
2314
|
+
return null;
|
|
2315
|
+
return node?.role && accepts.includes(node.role) ? null : StructureRefusal.NotAccepted;
|
|
2316
|
+
}
|
|
2218
2317
|
|
|
2219
2318
|
const SLIDE_VISIBLE = 'swiper-slide-visible';
|
|
2220
2319
|
const RUNTIME_KEY = '__ioxCarousel';
|
|
@@ -2400,7 +2499,7 @@ const SLIDE_BASE = {
|
|
|
2400
2499
|
width: '100%', height: '100%', aspectRatio: 'auto', display: 'flex', flexDirection: 'column', alignItems: 'center',
|
|
2401
2500
|
justifyContent: 'center', gap: '12px', padding: '56px 64px 16px 64px',
|
|
2402
2501
|
};
|
|
2403
|
-
const IMAGE_BASE = { width: '100%', height: 'auto', maxHeight: '72dvh', minHeight: '0px', objectFit: 'contain', aspectRatio: '4 / 3', flexShrink: '1' };
|
|
2502
|
+
const IMAGE_BASE$1 = { width: '100%', height: 'auto', maxHeight: '72dvh', minHeight: '0px', objectFit: 'contain', aspectRatio: '4 / 3', flexShrink: '1' };
|
|
2404
2503
|
const CAPTION_BASE = { textAlign: 'center', margin: ZERO, padding: ZERO, minHeight: 'auto', width: 'auto' };
|
|
2405
2504
|
/** The counter "3 / 6" sits at the START of the top row, Close at its end (board 6). */
|
|
2406
2505
|
const COUNTER_BASE = { color: 'var(--iox-text-muted)', top: '12px', alignItems: 'flex-start', justifyContent: 'flex-start' };
|
|
@@ -2421,7 +2520,7 @@ function bindRow(node, prop, source, field) {
|
|
|
2421
2520
|
}
|
|
2422
2521
|
function buildSlide(create, key, options, rowAlias) {
|
|
2423
2522
|
const slide = makeCompositePart(create, { key, type: SLIDE_TYPE, props: {}, elementStyle: NO_ELEMENT_STYLE }, null, 'Slide', SLIDE_BASE);
|
|
2424
|
-
const image = makeCompositePart(create, { key: `${key}-image`, type: 'Image', props: { src: '', alt: '' }, elementStyle: 'media' }, null, 'Image', IMAGE_BASE);
|
|
2523
|
+
const image = makeCompositePart(create, { key: `${key}-image`, type: 'Image', props: { src: '', alt: '' }, elementStyle: 'media' }, null, 'Image', IMAGE_BASE$1);
|
|
2425
2524
|
const caption = makeCompositePart(create, { key: `${key}-caption`, type: 'Text', props: { content: 'Caption', tag: 'p' }, elementStyle: 'small' }, null, 'Caption', CAPTION_BASE);
|
|
2426
2525
|
if (rowAlias) {
|
|
2427
2526
|
bindRow(image, 'src', rowAlias, options.imageField ?? 'item');
|
|
@@ -2525,13 +2624,897 @@ function rowIndexOf(el) {
|
|
|
2525
2624
|
return Number.isInteger(index) && index >= 0 ? index : 0;
|
|
2526
2625
|
}
|
|
2527
2626
|
|
|
2627
|
+
/**
|
|
2628
|
+
* TABS (CC4a — composite K2 T17–T20; complex-components boards 3 and 9, APPROVED 2026-10-20). Not a component: a
|
|
2629
|
+
* recipe of ordinary nodes, each part carrying a role, so every tab and every panel is selectable on the canvas, in
|
|
2630
|
+
* the layer tree and the breadcrumb, and wears its own element style (R2).
|
|
2631
|
+
*
|
|
2632
|
+
* Container `box` role tabs traits activateOn · orientation · ariaLabel (the list's name)
|
|
2633
|
+
* ├─ Container `tab-list` role tabs.list → role=tablist (locked) — or a Repeater (From content)
|
|
2634
|
+
* │ └─ ButtonBlock `tab` ×3 role tabs.trigger itemKey k1… → <button role=tab> › Text `button-label`
|
|
2635
|
+
* └─ Container `box` role tabs.panels (locked) — or a Repeater (From content)
|
|
2636
|
+
* └─ Container `box` ×3 role tabs.panel the SAME itemKey as its tab → role=tabpanel, hidden while off
|
|
2637
|
+
* ├─ Image `media` 4 / 3
|
|
2638
|
+
* └─ Text `body`
|
|
2639
|
+
*
|
|
2640
|
+
* A tab and its panel are a PAIR by `itemKey` — never by index, so a tab moved to the end still shows its own panel.
|
|
2641
|
+
* The behaviour is BUILT IN (composite plan D1 = B): exactly one item is on (`exclusive`), the APG tabs pattern with
|
|
2642
|
+
* AUTOMATIC activation (arrows move focus and activate, roving tabindex) — one runtime (`exclusive-runtime.ts`) on the
|
|
2643
|
+
* builder canvas and both engine paths, from the attribute planners below.
|
|
2644
|
+
*/
|
|
2645
|
+
var TabsBehaviourTrait;
|
|
2646
|
+
(function (TabsBehaviourTrait) {
|
|
2647
|
+
/** Which pointer gesture turns a tab on: a click (always) or also a hover. */
|
|
2648
|
+
TabsBehaviourTrait["ActivateOn"] = "activateOn";
|
|
2649
|
+
/** The tab list across the top, or beside the panels. */
|
|
2650
|
+
TabsBehaviourTrait["Orientation"] = "orientation";
|
|
2651
|
+
/** The tab list's accessible name (`aria-label` on the role=tablist element) — the root's, edited in Accessibility. */
|
|
2652
|
+
TabsBehaviourTrait["Label"] = "ariaLabel";
|
|
2653
|
+
})(TabsBehaviourTrait || (TabsBehaviourTrait = {}));
|
|
2654
|
+
var TabsActivateOn;
|
|
2655
|
+
(function (TabsActivateOn) {
|
|
2656
|
+
TabsActivateOn["Click"] = "click";
|
|
2657
|
+
TabsActivateOn["Hover"] = "hover";
|
|
2658
|
+
})(TabsActivateOn || (TabsActivateOn = {}));
|
|
2659
|
+
var TabsOrientation;
|
|
2660
|
+
(function (TabsOrientation) {
|
|
2661
|
+
TabsOrientation["Horizontal"] = "horizontal";
|
|
2662
|
+
TabsOrientation["Vertical"] = "vertical";
|
|
2663
|
+
})(TabsOrientation || (TabsOrientation = {}));
|
|
2664
|
+
const TABS_ACTIVATE_ONS = [TabsActivateOn.Click, TabsActivateOn.Hover];
|
|
2665
|
+
const TABS_ORIENTATIONS = [TabsOrientation.Horizontal, TabsOrientation.Vertical];
|
|
2666
|
+
/** Tabs from content default to six rows: more tabs do not fit a row (board 9 Q13). */
|
|
2667
|
+
const TABS_CONTENT_LIMIT = 6;
|
|
2668
|
+
/** The breakpoint whose layer stacks a VERTICAL tabs (the list above the panels) and a panel's picture above its words. */
|
|
2669
|
+
const TABS_STACK_BREAKPOINT = CARD_STACK_BREAKPOINT;
|
|
2670
|
+
/** The words a fresh Tabs starts with — generic, the author's to replace (Card v2.1: never invented facts). */
|
|
2671
|
+
const TABS_WORDS = [
|
|
2672
|
+
{ label: 'First tab', text: 'Say what this tab is about in a sentence or two. Its picture sits beside it.' },
|
|
2673
|
+
{ label: 'Second tab', text: 'Say what this tab is about in a sentence or two. Its picture sits beside it.' },
|
|
2674
|
+
{ label: 'Third tab', text: 'Say what this tab is about in a sentence or two. Its picture sits beside it.' },
|
|
2675
|
+
];
|
|
2676
|
+
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;
|
|
2678
|
+
(function (Type) {
|
|
2679
|
+
Type["Container"] = "Container";
|
|
2680
|
+
Type["ButtonBlock"] = "ButtonBlock";
|
|
2681
|
+
Type["Text"] = "Text";
|
|
2682
|
+
Type["Image"] = "Image";
|
|
2683
|
+
Type["Repeater"] = "Repeater";
|
|
2684
|
+
})(Type || (Type = {}));
|
|
2685
|
+
var Style;
|
|
2686
|
+
(function (Style) {
|
|
2687
|
+
Style["Root"] = "box";
|
|
2688
|
+
Style["List"] = "tab-list";
|
|
2689
|
+
Style["Tab"] = "tab";
|
|
2690
|
+
Style["Label"] = "button-label";
|
|
2691
|
+
Style["Panels"] = "box";
|
|
2692
|
+
Style["Panel"] = "box";
|
|
2693
|
+
Style["Media"] = "media";
|
|
2694
|
+
Style["Body"] = "body";
|
|
2695
|
+
})(Style || (Style = {}));
|
|
2696
|
+
const ZERO_BOX = '0px 0px 0px 0px';
|
|
2697
|
+
/** 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);
|
|
2699
|
+
/** What each part holds — layout only; its LOOK is its element style. The list WRAPS at narrow widths (board 3). */
|
|
2700
|
+
const PART_BASE = {
|
|
2701
|
+
[TabsRole.Tabs]: { display: 'flex', width: '100%', minHeight: 'auto', height: 'auto' },
|
|
2702
|
+
[TabsRole.List]: { display: 'flex', gap: '4px', minHeight: 'auto', height: 'auto' },
|
|
2703
|
+
[TabsRole.Trigger]: { width: 'auto', minHeight: 'auto', height: 'auto', cursor: 'pointer' },
|
|
2704
|
+
[TabsRole.Panels]: { display: 'block', width: '100%', minWidth: '0px', minHeight: 'auto', height: 'auto' },
|
|
2705
|
+
// Board 3: a panel is any nodes at all — the recipe's is a picture beside its words.
|
|
2706
|
+
[TabsRole.Panel]: { display: 'grid', gridTemplateColumns: '1fr 1.2fr', gap: '24px', alignItems: 'center', width: '100%', minHeight: 'auto', height: 'auto' },
|
|
2707
|
+
};
|
|
2708
|
+
const LABEL_BASE = { width: 'auto', minHeight: 'auto', padding: ZERO_BOX, margin: ZERO_BOX };
|
|
2709
|
+
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' };
|
|
2711
|
+
/** On a phone a panel's picture sits above its words. */
|
|
2712
|
+
const PANEL_STACK = { gridTemplateColumns: '1fr' };
|
|
2713
|
+
const ORIENTATION_LAYOUT = {
|
|
2714
|
+
[TabsOrientation.Horizontal]: {
|
|
2715
|
+
root: { flexDirection: 'column', alignItems: 'stretch', gap: '20px' },
|
|
2716
|
+
list: { flexDirection: 'row', flexWrap: 'wrap', width: '100%', flexShrink: '1', borderWidth: LIST_EDGE },
|
|
2717
|
+
},
|
|
2718
|
+
[TabsOrientation.Vertical]: {
|
|
2719
|
+
root: { flexDirection: 'row', alignItems: 'flex-start', gap: '24px' },
|
|
2720
|
+
list: { flexDirection: 'column', flexWrap: 'nowrap', width: 'auto', flexShrink: '0', borderWidth: ZERO_BOX },
|
|
2721
|
+
rootStack: { flexDirection: 'column', alignItems: 'stretch', gap: '20px' },
|
|
2722
|
+
listStack: { width: '100%' },
|
|
2723
|
+
},
|
|
2724
|
+
};
|
|
2725
|
+
const ROOT_STACK_KEYS = ['flexDirection', 'alignItems', 'gap'];
|
|
2726
|
+
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);
|
|
2729
|
+
}
|
|
2730
|
+
/** One tab and its panel, sharing `itemKey`. */
|
|
2731
|
+
function buildTabPair(create, words, itemKey) {
|
|
2732
|
+
const tab = make(create, `${itemKey}-tab`, Type.ButtonBlock, TabsRole.Trigger, Style.Tab, {}, 'Tab');
|
|
2733
|
+
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);
|
|
2736
|
+
tab.children = [label];
|
|
2737
|
+
const panel = make(create, `${itemKey}-panel`, Type.Container, TabsRole.Panel, Style.Panel, {}, 'Panel');
|
|
2738
|
+
panel.itemKey = itemKey;
|
|
2739
|
+
writeLayerStyles(panel, TABS_STACK_BREAKPOINT, PANEL_STACK);
|
|
2740
|
+
const image = make(create, `${itemKey}-image`, Type.Image, null, Style.Media, { src: '', alt: '' }, 'Image');
|
|
2741
|
+
writeStyles(image, IMAGE_BASE);
|
|
2742
|
+
const text = make(create, `${itemKey}-text`, Type.Text, null, Style.Body, { content: words.text, tag: 'p' }, 'Text');
|
|
2743
|
+
writeStyles(text, BODY_BASE);
|
|
2744
|
+
panel.children = [image, text];
|
|
2745
|
+
return { tab, panel };
|
|
2746
|
+
}
|
|
2747
|
+
/** A fresh Tabs: three tabs, the first on, activated by a click, the list across the top (board 3). */
|
|
2748
|
+
function buildTabsRecipe(create, options = {}) {
|
|
2749
|
+
const root = make(create, 'tabs', Type.Container, TabsRole.Tabs, Style.Root, {}, 'Tabs');
|
|
2750
|
+
writeProp(root, TabsBehaviourTrait.ActivateOn, options.activateOn ?? TabsActivateOn.Click);
|
|
2751
|
+
writeProp(root, TabsBehaviourTrait.Orientation, options.orientation ?? TabsOrientation.Horizontal);
|
|
2752
|
+
if (options.label)
|
|
2753
|
+
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');
|
|
2756
|
+
const pairs = (options.tabs ?? TABS_WORDS).map((w, i) => buildTabPair(create, w, `k${i + 1}`));
|
|
2757
|
+
list.children = pairs.map(p => p.tab);
|
|
2758
|
+
panels.children = pairs.map(p => p.panel);
|
|
2759
|
+
root.children = [list, panels];
|
|
2760
|
+
applyOrientation(root, options.orientation ?? TabsOrientation.Horizontal);
|
|
2761
|
+
return root;
|
|
2762
|
+
}
|
|
2763
|
+
// ── reading a tree ──────────────────────────────────────────────────────────────────────────────
|
|
2764
|
+
function isTabsRoot(node) {
|
|
2765
|
+
return node?.role === TabsRole.Tabs;
|
|
2766
|
+
}
|
|
2767
|
+
/** The tab list (the holder of the triggers). */
|
|
2768
|
+
function tabsListOf(root) {
|
|
2769
|
+
return (root.children ?? []).find(c => c.role === TabsRole.List);
|
|
2770
|
+
}
|
|
2771
|
+
/** The holder of the panels. */
|
|
2772
|
+
function tabsPanelsOf(root) {
|
|
2773
|
+
return (root.children ?? []).find(c => c.role === TabsRole.Panels);
|
|
2774
|
+
}
|
|
2775
|
+
/** The tabs in order — every tab (Manual), or the one template tab (From content). */
|
|
2776
|
+
function tabsTriggers(root) {
|
|
2777
|
+
return (tabsListOf(root)?.children ?? []).filter(c => c.role === TabsRole.Trigger);
|
|
2778
|
+
}
|
|
2779
|
+
/** The panels in their holder's order (their order does not pair them — their key does). */
|
|
2780
|
+
function tabsPanels(root) {
|
|
2781
|
+
return (tabsPanelsOf(root)?.children ?? []).filter(c => c.role === TabsRole.Panel);
|
|
2782
|
+
}
|
|
2783
|
+
/** True when the Tabs takes its tabs from content (its list is a Repeater, R3). */
|
|
2784
|
+
function isTabsFromContent(root) {
|
|
2785
|
+
return !!root && tabsListOf(root)?.type === Type.Repeater;
|
|
2786
|
+
}
|
|
2787
|
+
/** The panel paired with `tab` (the same key), or undefined. From content the one template panel pairs the template tab. */
|
|
2788
|
+
function tabPanelOf(root, tab) {
|
|
2789
|
+
if (isTabsFromContent(root))
|
|
2790
|
+
return tabsPanels(root)[0];
|
|
2791
|
+
return tab.itemKey ? tabsPanels(root).find(p => p.itemKey === tab.itemKey) : undefined;
|
|
2792
|
+
}
|
|
2793
|
+
/** The tab paired with `panel` (the same key), or undefined. */
|
|
2794
|
+
function tabTriggerOf(root, panel) {
|
|
2795
|
+
if (isTabsFromContent(root))
|
|
2796
|
+
return tabsTriggers(root)[0];
|
|
2797
|
+
return panel.itemKey ? tabsTriggers(root).find(t => t.itemKey === panel.itemKey) : undefined;
|
|
2798
|
+
}
|
|
2799
|
+
/** The other half of a tab ⇄ panel pair, or undefined for any other node. */
|
|
2800
|
+
function tabsPartnerOf(root, node) {
|
|
2801
|
+
if (node.role === TabsRole.Trigger)
|
|
2802
|
+
return tabPanelOf(root, node);
|
|
2803
|
+
if (node.role === TabsRole.Panel)
|
|
2804
|
+
return tabTriggerOf(root, node);
|
|
2805
|
+
return undefined;
|
|
2806
|
+
}
|
|
2807
|
+
/** The words a tab shows: its first Text's. */
|
|
2808
|
+
function tabLabelNodeOf(tab) {
|
|
2809
|
+
let hit;
|
|
2810
|
+
walkComposite(tab, n => { if (!hit && n !== tab && n.type === Type.Text)
|
|
2811
|
+
hit = n; });
|
|
2812
|
+
return hit;
|
|
2813
|
+
}
|
|
2814
|
+
const stripTags = (v) => String(v ?? '').replace(/<[^>]*>/g, '').trim();
|
|
2815
|
+
/** A tab's name in the Items list, the layer tree and the item-action picker: its words. */
|
|
2816
|
+
function tabLabel(tab) {
|
|
2817
|
+
const label = tabLabelNodeOf(tab);
|
|
2818
|
+
return label ? stripTags(readProp(label, 'content')) : '';
|
|
2819
|
+
}
|
|
2820
|
+
/** Items ▸ rename: the tab's words. */
|
|
2821
|
+
function renameTab(tab, words) {
|
|
2822
|
+
const label = tabLabelNodeOf(tab);
|
|
2823
|
+
return !!label && writeProp(label, 'content', words);
|
|
2824
|
+
}
|
|
2825
|
+
/** The first Text inside a panel (the words From content binds to). */
|
|
2826
|
+
function tabPanelTextOf(panel) {
|
|
2827
|
+
let hit;
|
|
2828
|
+
walkComposite(panel, n => { if (!hit && n !== panel && n.type === Type.Text)
|
|
2829
|
+
hit = n; });
|
|
2830
|
+
return hit;
|
|
2831
|
+
}
|
|
2832
|
+
/** The first Image inside a panel. */
|
|
2833
|
+
function tabPanelImageOf(panel) {
|
|
2834
|
+
let hit;
|
|
2835
|
+
walkComposite(panel, n => { if (!hit && n.type === Type.Image)
|
|
2836
|
+
hit = n; });
|
|
2837
|
+
return hit;
|
|
2838
|
+
}
|
|
2839
|
+
/** The root's behaviour (an unset trait reads its default: a click, the list across the top). */
|
|
2840
|
+
function tabsBehaviour(root) {
|
|
2841
|
+
const on = root ? readProp(root, TabsBehaviourTrait.ActivateOn) : undefined;
|
|
2842
|
+
const orientation = root ? readProp(root, TabsBehaviourTrait.Orientation) : undefined;
|
|
2843
|
+
return {
|
|
2844
|
+
activateOn: TABS_ACTIVATE_ONS.includes(on) ? on : TabsActivateOn.Click,
|
|
2845
|
+
orientation: TABS_ORIENTATIONS.includes(orientation) ? orientation : TabsOrientation.Horizontal,
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2848
|
+
/** The tab list's accessible name (empty when unnamed). */
|
|
2849
|
+
function tabsLabel(root) {
|
|
2850
|
+
return root ? String(readProp(root, TabsBehaviourTrait.Label) ?? '').trim() : '';
|
|
2851
|
+
}
|
|
2852
|
+
/** Write the orientation preset into the root's and the list's layout (both orientations write the same keys). */
|
|
2853
|
+
function applyOrientation(root, orientation) {
|
|
2854
|
+
const spec = ORIENTATION_LAYOUT[orientation];
|
|
2855
|
+
const list = tabsListOf(root);
|
|
2856
|
+
writeStyles(root, spec.root);
|
|
2857
|
+
if (list)
|
|
2858
|
+
writeStyles(list, spec.list);
|
|
2859
|
+
if (spec.rootStack)
|
|
2860
|
+
writeLayerStyles(root, TABS_STACK_BREAKPOINT, spec.rootStack);
|
|
2861
|
+
else
|
|
2862
|
+
clearLayerStyles(root, TABS_STACK_BREAKPOINT, ROOT_STACK_KEYS);
|
|
2863
|
+
if (list) {
|
|
2864
|
+
if (spec.listStack)
|
|
2865
|
+
writeLayerStyles(list, TABS_STACK_BREAKPOINT, spec.listStack);
|
|
2866
|
+
else
|
|
2867
|
+
clearLayerStyles(list, TABS_STACK_BREAKPOINT, LIST_STACK_KEYS);
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
/** Behaviour ▸ Activate on / Orientation (and Accessibility ▸ the list's name). False when the root has no such trait. */
|
|
2871
|
+
function setTabsBehaviour(root, patch) {
|
|
2872
|
+
let ok = true;
|
|
2873
|
+
if (patch.activateOn !== undefined)
|
|
2874
|
+
ok = writeProp(root, TabsBehaviourTrait.ActivateOn, patch.activateOn) && ok;
|
|
2875
|
+
if (patch.orientation !== undefined) {
|
|
2876
|
+
ok = writeProp(root, TabsBehaviourTrait.Orientation, patch.orientation) && ok;
|
|
2877
|
+
applyOrientation(root, patch.orientation);
|
|
2878
|
+
}
|
|
2879
|
+
if (patch.label !== undefined)
|
|
2880
|
+
ok = writeProp(root, TabsBehaviourTrait.Label, patch.label) && ok;
|
|
2881
|
+
return ok;
|
|
2882
|
+
}
|
|
2883
|
+
// ── editing (Items list, selection toolbar ▸ Add tab, the structure guard) ──────────────────────
|
|
2884
|
+
/** A key no tab of `root` holds yet (`k4`…). */
|
|
2885
|
+
function nextTabKey(root) {
|
|
2886
|
+
const taken = new Set([...tabsTriggers(root), ...tabsPanels(root)].map(n => n.itemKey).filter(Boolean));
|
|
2887
|
+
let n = tabsTriggers(root).length + 1;
|
|
2888
|
+
while (taken.has(`k${n}`))
|
|
2889
|
+
n++;
|
|
2890
|
+
return `k${n}`;
|
|
2891
|
+
}
|
|
2892
|
+
/** Items ▸ Add tab (and the toolbar's "Add tab"): a fresh tab after `after` (or at the end) AND its panel. */
|
|
2893
|
+
function addTabPair(root, create, after, words = TABS_NEW_TAB_WORDS) {
|
|
2894
|
+
const list = tabsListOf(root);
|
|
2895
|
+
const panels = tabsPanelsOf(root);
|
|
2896
|
+
if (!list || !panels || isTabsFromContent(root))
|
|
2897
|
+
return null;
|
|
2898
|
+
const pair = buildTabPair(create, words, nextTabKey(root));
|
|
2899
|
+
const tabs = [...(list.children ?? [])];
|
|
2900
|
+
const afterTab = after ? (after.role === TabsRole.Panel ? tabTriggerOf(root, after) : after) : null;
|
|
2901
|
+
const at = afterTab ? tabs.indexOf(afterTab) : -1;
|
|
2902
|
+
tabs.splice(at < 0 ? tabs.length : at + 1, 0, pair.tab);
|
|
2903
|
+
list.children = tabs;
|
|
2904
|
+
const afterPanel = afterTab ? tabPanelOf(root, afterTab) : null;
|
|
2905
|
+
const held = [...(panels.children ?? [])];
|
|
2906
|
+
const pAt = afterPanel ? held.indexOf(afterPanel) : -1;
|
|
2907
|
+
held.splice(pAt < 0 ? held.length : pAt + 1, 0, pair.panel);
|
|
2908
|
+
panels.children = held;
|
|
2909
|
+
return pair;
|
|
2910
|
+
}
|
|
2911
|
+
function moveIn(holder, node, toIndex, role) {
|
|
2912
|
+
const children = [...(holder.children ?? [])];
|
|
2913
|
+
const from = children.indexOf(node);
|
|
2914
|
+
if (from < 0)
|
|
2915
|
+
return;
|
|
2916
|
+
children.splice(from, 1);
|
|
2917
|
+
const same = children.filter(c => c.role === role);
|
|
2918
|
+
const anchor = same[Math.max(0, Math.min(toIndex, same.length))];
|
|
2919
|
+
children.splice(anchor ? children.indexOf(anchor) : children.length, 0, node);
|
|
2920
|
+
holder.children = children;
|
|
2921
|
+
}
|
|
2922
|
+
/** Items ▸ drag to reorder: move a tab — and its panel, so the layer tree reads in the same order — to `toIndex`.
|
|
2923
|
+
* The pairing never depended on the order. False when `tab` is not one of the root's tabs. */
|
|
2924
|
+
function moveTabPair(root, tab, toIndex) {
|
|
2925
|
+
const list = tabsListOf(root);
|
|
2926
|
+
const panels = tabsPanelsOf(root);
|
|
2927
|
+
if (!list || tab.role !== TabsRole.Trigger || !(list.children ?? []).includes(tab))
|
|
2928
|
+
return false;
|
|
2929
|
+
const panel = tabPanelOf(root, tab);
|
|
2930
|
+
moveIn(list, tab, toIndex, TabsRole.Trigger);
|
|
2931
|
+
if (panel && panels)
|
|
2932
|
+
moveIn(panels, panel, toIndex, TabsRole.Panel);
|
|
2933
|
+
return true;
|
|
2934
|
+
}
|
|
2935
|
+
/** Remove a tab and its panel (the pair) — what "Delete tab and its panel" does. True when the tab was removed. */
|
|
2936
|
+
function removeTabPair(root, node) {
|
|
2937
|
+
const tab = node.role === TabsRole.Panel ? tabTriggerOf(root, node) : node;
|
|
2938
|
+
const list = tabsListOf(root);
|
|
2939
|
+
if (!tab || !list || tab.role !== TabsRole.Trigger)
|
|
2940
|
+
return false;
|
|
2941
|
+
const panel = tabPanelOf(root, tab);
|
|
2942
|
+
list.children = (list.children ?? []).filter(c => c !== tab);
|
|
2943
|
+
const panels = tabsPanelsOf(root);
|
|
2944
|
+
if (panel && panels)
|
|
2945
|
+
panels.children = (panels.children ?? []).filter(c => c !== panel);
|
|
2946
|
+
return true;
|
|
2947
|
+
}
|
|
2948
|
+
var TabsEditRefusal;
|
|
2949
|
+
(function (TabsEditRefusal) {
|
|
2950
|
+
/** A Tabs keeps at least one tab (composite plan T18). */
|
|
2951
|
+
TabsEditRefusal["LastTab"] = "last-tab";
|
|
2952
|
+
})(TabsEditRefusal || (TabsEditRefusal = {}));
|
|
2953
|
+
/** Why removing `node` (a tab or a panel) from `root` is refused, or null. */
|
|
2954
|
+
function tabsRemovalRefusal(root, node) {
|
|
2955
|
+
if (!root || (node?.role !== TabsRole.Trigger && node?.role !== TabsRole.Panel))
|
|
2956
|
+
return null;
|
|
2957
|
+
return tabsTriggers(root).length <= 1 ? TabsEditRefusal.LastTab : null;
|
|
2958
|
+
}
|
|
2959
|
+
/** Every "Item open" (`trigger-on`) state inside a tab or a panel points at ITS tab or panel — the part that carries
|
|
2960
|
+
* `data-iox-on` while its item is on. */
|
|
2961
|
+
function tabsItemTriggerIds(root) {
|
|
2962
|
+
for (const part of [...tabsTriggers(root), ...tabsPanels(root)]) {
|
|
2963
|
+
if (!part.id)
|
|
2964
|
+
continue;
|
|
2965
|
+
walkComposite(part, n => {
|
|
2966
|
+
if (n === part || !n.stateStyles?.[ITEM_OPEN_STATE])
|
|
2967
|
+
return;
|
|
2968
|
+
n.stateTriggers = { ...(n.stateTriggers ?? {}), [ITEM_OPEN_STATE]: part.id };
|
|
2969
|
+
});
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
function tabsScope(rootId, behaviour, keys, label = '') {
|
|
2973
|
+
return { rootId, behaviour, keys, active: keys[0] ?? null, label };
|
|
2974
|
+
}
|
|
2975
|
+
/** The item keys a manual Tabs pairs by — its tabs' own keys, in order (a tab with none by its index). */
|
|
2976
|
+
function manualTabKeys(root) {
|
|
2977
|
+
return tabsTriggers(root).map((t, i) => t.itemKey || `item-${i}`);
|
|
2978
|
+
}
|
|
2979
|
+
/** One item's render state: its key, whether it is on, and its ARIA ids (`iox-tab-<scope>-<key>-h` / `-p`). */
|
|
2980
|
+
function tabsItemState(scope, key) {
|
|
2981
|
+
return { key, on: scope.active === key, ids: accordionDomIds(scope.rootId, key, CompositeKind.Tabs) };
|
|
2982
|
+
}
|
|
2983
|
+
const ACTIVATE_ON_ATTRIBUTE = 'data-iox-activate-on';
|
|
2984
|
+
const ORIENTATION_ATTRIBUTE = 'data-iox-orientation';
|
|
2985
|
+
function tabsRootAttributes(behaviour, scopeId) {
|
|
2986
|
+
return {
|
|
2987
|
+
[COMPOSITE_ATTRIBUTE]: CompositeKind.Tabs,
|
|
2988
|
+
[ACTIVATE_ON_ATTRIBUTE]: behaviour.activateOn,
|
|
2989
|
+
[ORIENTATION_ATTRIBUTE]: behaviour.orientation,
|
|
2990
|
+
...(scopeId ? { [SCOPE_ATTRIBUTE]: scopeId } : {}),
|
|
2991
|
+
};
|
|
2992
|
+
}
|
|
2993
|
+
/** The tab list: `role=tablist`, vertical said aloud, named by the root's label. */
|
|
2994
|
+
function tabListAttributes(behaviour, label = '') {
|
|
2995
|
+
return {
|
|
2996
|
+
role: 'tablist',
|
|
2997
|
+
...(behaviour.orientation === TabsOrientation.Vertical ? { 'aria-orientation': 'vertical' } : {}),
|
|
2998
|
+
...(label ? { 'aria-label': label } : {}),
|
|
2999
|
+
};
|
|
3000
|
+
}
|
|
3001
|
+
/** A tab: a real button that selects its panel — selected = the one tab in the Tab order (roving tabindex). */
|
|
3002
|
+
function tabTriggerAttributes(state) {
|
|
3003
|
+
return {
|
|
3004
|
+
type: 'button',
|
|
3005
|
+
role: 'tab',
|
|
3006
|
+
id: state.ids.trigger,
|
|
3007
|
+
'aria-selected': state.on ? 'true' : 'false',
|
|
3008
|
+
'aria-controls': state.ids.panel,
|
|
3009
|
+
tabindex: state.on ? '0' : '-1',
|
|
3010
|
+
[ITEM_KEY_ATTRIBUTE]: state.key,
|
|
3011
|
+
...(state.on ? { [ON_ATTRIBUTE]: '' } : {}),
|
|
3012
|
+
};
|
|
3013
|
+
}
|
|
3014
|
+
/** A panel: `role=tabpanel`, named by its tab, a Tab stop of its own, and `hidden` (display:none) while off. */
|
|
3015
|
+
function tabPanelAttributes(state) {
|
|
3016
|
+
return {
|
|
3017
|
+
role: 'tabpanel',
|
|
3018
|
+
id: state.ids.panel,
|
|
3019
|
+
'aria-labelledby': state.ids.trigger,
|
|
3020
|
+
tabindex: '0',
|
|
3021
|
+
[ITEM_KEY_ATTRIBUTE]: state.key,
|
|
3022
|
+
...(state.on ? { [ON_ATTRIBUTE]: '' } : { hidden: '' }),
|
|
3023
|
+
};
|
|
3024
|
+
}
|
|
3025
|
+
/**
|
|
3026
|
+
* The render scope of one rendered Tabs — ONE decision for both engine paths (and the composer's checks): its scope id
|
|
3027
|
+
* (its node id + the rows around it, vectoriox/iox-ui#20), its behaviour, its list's name and every item key in order —
|
|
3028
|
+
* a manual tab's own `itemKey`, or, From content, each ROW's own id (R3: never its index — a reorder in the CMS keeps
|
|
3029
|
+
* every tab with its own panel). `rows` = the rows the tab list's Repeater resolved (null for a manual Tabs).
|
|
3030
|
+
*/
|
|
3031
|
+
function tabsScopeFor(root, rootCssId, rows, rowKeys = []) {
|
|
3032
|
+
const keys = rows ? rows.map((row, i) => rowItemKey(row, i)) : manualTabKeys(root);
|
|
3033
|
+
return tabsScope(compositeScopeId(rootCssId, rowKeys), tabsBehaviour(root), keys, tabsLabel(root));
|
|
3034
|
+
}
|
|
3035
|
+
/** The key a manual tab or panel renders with: its own key, else its index among its kind. */
|
|
3036
|
+
function tabPartKey(node, index) {
|
|
3037
|
+
return node.itemKey || `item-${index}`;
|
|
3038
|
+
}
|
|
3039
|
+
/**
|
|
3040
|
+
* The attributes a part of a Tabs writes on its own element (both engine paths, SSR included): the root's data
|
|
3041
|
+
* attributes (its scope id when it sits in repeated rows), the list's role and name, a tab's and a panel's state by
|
|
3042
|
+
* `key` (its own key, or — From content — its row's). Anything else writes nothing.
|
|
3043
|
+
*/
|
|
3044
|
+
function tabsPartAttributes(node, scope, key, rowScoped = false) {
|
|
3045
|
+
switch (node.role) {
|
|
3046
|
+
case TabsRole.Tabs: return tabsRootAttributes(scope.behaviour, rowScoped ? scope.rootId : undefined);
|
|
3047
|
+
case TabsRole.List: return tabListAttributes(scope.behaviour, scope.label);
|
|
3048
|
+
case TabsRole.Trigger: return key ? tabTriggerAttributes(tabsItemState(scope, key)) : {};
|
|
3049
|
+
case TabsRole.Panel: return key ? tabPanelAttributes(tabsItemState(scope, key)) : {};
|
|
3050
|
+
default: return {};
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
/**
|
|
3054
|
+
* The APG tabs keys on a tab (automatic activation): → / ← move along a horizontal list — mirrored under RTL — and ↓ /
|
|
3055
|
+
* ↑ along a vertical one; Home / End jump. Every move wraps. Enter / Space are the button's own.
|
|
3056
|
+
*/
|
|
3057
|
+
function tabsKeyAction(key, orientation, rtl = false) {
|
|
3058
|
+
if (key === 'Home')
|
|
3059
|
+
return AccordionKeyAction.First;
|
|
3060
|
+
if (key === 'End')
|
|
3061
|
+
return AccordionKeyAction.Last;
|
|
3062
|
+
if (orientation === TabsOrientation.Vertical) {
|
|
3063
|
+
if (key === 'ArrowDown')
|
|
3064
|
+
return AccordionKeyAction.Next;
|
|
3065
|
+
if (key === 'ArrowUp')
|
|
3066
|
+
return AccordionKeyAction.Previous;
|
|
3067
|
+
return null;
|
|
3068
|
+
}
|
|
3069
|
+
if (key === 'ArrowRight')
|
|
3070
|
+
return rtl ? AccordionKeyAction.Previous : AccordionKeyAction.Next;
|
|
3071
|
+
if (key === 'ArrowLeft')
|
|
3072
|
+
return rtl ? AccordionKeyAction.Next : AccordionKeyAction.Previous;
|
|
3073
|
+
return null;
|
|
3074
|
+
}
|
|
3075
|
+
// ── Items from content (R3, composite T39) ──────────────────────────────────────────────────────
|
|
3076
|
+
const SHORT_TEXT = new Set(['text']);
|
|
3077
|
+
const LONG_TEXT = new Set(['textarea', 'richText', 'editor']);
|
|
3078
|
+
const IMAGE_CONTROLLERS = new Set(['image', 'media', 'file']);
|
|
3079
|
+
/** R3 (board 3 "Tabs from content"): switching to From content binds the tab's words to the first short-text field,
|
|
3080
|
+
* the panel's words to the first long-text field and its picture to the first image field. ONE rule for the builder's
|
|
3081
|
+
* switch and the composer. */
|
|
3082
|
+
function pickTabsAutoBinds(fields) {
|
|
3083
|
+
const isString = (f) => !f.type || f.type === 'string';
|
|
3084
|
+
const label = fields.find(f => isString(f) && SHORT_TEXT.has(String(f.controller ?? 'text')))?.name ?? null;
|
|
3085
|
+
const text = fields.find(f => isString(f) && LONG_TEXT.has(String(f.controller ?? '')) && f.name !== label)?.name ?? null;
|
|
3086
|
+
const image = fields.find(f => IMAGE_CONTROLLERS.has(String(f.controller ?? '')) && f.name !== label && f.name !== text)?.name ?? null;
|
|
3087
|
+
return { label, text, image };
|
|
3088
|
+
}
|
|
3089
|
+
/** The bound props of a tab's words, a panel's words and its picture. */
|
|
3090
|
+
const TABS_LABEL_BIND_PROP = 'content';
|
|
3091
|
+
const TABS_TEXT_BIND_PROP = 'content';
|
|
3092
|
+
const TABS_IMAGE_BIND_PROP = 'src';
|
|
3093
|
+
/**
|
|
3094
|
+
* Manual → From content (board 9 "Items: Manual · From content"): the list becomes `listRepeater` and the panels
|
|
3095
|
+
* holder `panelsRepeater` (Repeater nodes the caller created with the same ids, roles, names, element styles and
|
|
3096
|
+
* styles — a type change is the consumer's). The FIRST tab and ITS panel become the templates (their parts, styles
|
|
3097
|
+
* and interactions kept; their keys dropped — a row's key is its id); every other pair is removed. Returns the removed
|
|
3098
|
+
* nodes.
|
|
3099
|
+
*/
|
|
3100
|
+
function tabsToContent(root, listRepeater, panelsRepeater) {
|
|
3101
|
+
const [tab, ...restTabs] = tabsTriggers(root);
|
|
3102
|
+
const panel = tab ? tabPanelOf(root, tab) : undefined;
|
|
3103
|
+
const restPanels = tabsPanels(root).filter(p => p !== panel);
|
|
3104
|
+
if (tab)
|
|
3105
|
+
delete tab.itemKey;
|
|
3106
|
+
if (panel)
|
|
3107
|
+
delete panel.itemKey;
|
|
3108
|
+
listRepeater.role = TabsRole.List;
|
|
3109
|
+
panelsRepeater.role = TabsRole.Panels;
|
|
3110
|
+
listRepeater.children = tab ? [tab] : [];
|
|
3111
|
+
panelsRepeater.children = panel ? [panel] : [];
|
|
3112
|
+
root.children = (root.children ?? []).map(c => (c.role === TabsRole.List ? listRepeater : c.role === TabsRole.Panels ? panelsRepeater : c));
|
|
3113
|
+
return [...restTabs, ...restPanels];
|
|
3114
|
+
}
|
|
3115
|
+
function writeRowIn(node, row, resolve) {
|
|
3116
|
+
walkComposite(node, n => {
|
|
3117
|
+
const bindings = n.bindings ?? [];
|
|
3118
|
+
for (const b of bindings) {
|
|
3119
|
+
const value = resolve(row, b);
|
|
3120
|
+
if (value !== undefined && value !== null)
|
|
3121
|
+
writeProp(n, b.prop, value);
|
|
3122
|
+
}
|
|
3123
|
+
if (bindings.length)
|
|
3124
|
+
n.bindings = [];
|
|
3125
|
+
});
|
|
3126
|
+
}
|
|
3127
|
+
/**
|
|
3128
|
+
* From content → Manual: the rows come in as static pairs — each a copy of the template tab and panel (`clone`, which
|
|
3129
|
+
* gives the copy fresh ids) whose bound words are written in as its own and whose bindings are dropped. `list` and
|
|
3130
|
+
* `panels` are the manual holders the caller created. Returns the new pairs.
|
|
3131
|
+
*/
|
|
3132
|
+
function tabsToManual(root, list, panels, rows, clone, resolve) {
|
|
3133
|
+
const [tabTemplate] = tabsTriggers(root);
|
|
3134
|
+
const panelTemplate = tabsPanels(root)[0];
|
|
3135
|
+
list.role = TabsRole.List;
|
|
3136
|
+
panels.role = TabsRole.Panels;
|
|
3137
|
+
const pairs = tabTemplate && panelTemplate ? rows.map((row, i) => {
|
|
3138
|
+
const tab = clone(tabTemplate);
|
|
3139
|
+
const panel = clone(panelTemplate);
|
|
3140
|
+
tab.itemKey = panel.itemKey = `k${i + 1}`;
|
|
3141
|
+
writeRowIn(tab, row, resolve);
|
|
3142
|
+
writeRowIn(panel, row, resolve);
|
|
3143
|
+
return { tab, panel };
|
|
3144
|
+
}) : [];
|
|
3145
|
+
list.children = pairs.map(p => p.tab);
|
|
3146
|
+
panels.children = pairs.map(p => p.panel);
|
|
3147
|
+
root.children = (root.children ?? []).map(c => (c.role === TabsRole.List ? list : c.role === TabsRole.Panels ? panels : c));
|
|
3148
|
+
return pairs;
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
const KIND_ROOT = (kind) => `[${COMPOSITE_ATTRIBUTE}="${kind}"]`;
|
|
3152
|
+
const EXCLUSIVE_KINDS = Object.keys(EXCLUSIVE_KIND_ROLES);
|
|
3153
|
+
const ROOT_SELECTOR = EXCLUSIVE_KINDS.map(KIND_ROOT).join(', ');
|
|
3154
|
+
const roleSelector = (pick) => EXCLUSIVE_KINDS.map(k => EXCLUSIVE_KIND_ROLES[k][pick]).filter((r) => !!r).map(r => `.${roleClass(r)}`).join(', ');
|
|
3155
|
+
const LIST_SELECTOR = roleSelector('list');
|
|
3156
|
+
const TRIGGER_SELECTOR = roleSelector('trigger');
|
|
3157
|
+
const PANEL_SELECTOR = roleSelector('panel');
|
|
3158
|
+
const ITEM_PART_SELECTOR = `${TRIGGER_SELECTOR}, ${PANEL_SELECTOR}`;
|
|
3159
|
+
const TABLIST_SELECTOR = '[role="tablist"]';
|
|
3160
|
+
/** The exclusive composite an element belongs to (the element itself when it is a root). */
|
|
3161
|
+
function closestExclusive(el) {
|
|
3162
|
+
return el?.closest?.(ROOT_SELECTOR) ?? null;
|
|
3163
|
+
}
|
|
3164
|
+
/** The trigger or panel an element sits in (itself when it is one) — its ITEM part — or null. */
|
|
3165
|
+
function closestExclusiveItemPart(el) {
|
|
3166
|
+
return el?.closest?.(ITEM_PART_SELECTOR) ?? null;
|
|
3167
|
+
}
|
|
3168
|
+
const kindOf = (root) => {
|
|
3169
|
+
const raw = root.getAttribute(COMPOSITE_ATTRIBUTE);
|
|
3170
|
+
return isExclusiveKind(raw) ? raw : CompositeKind.Tabs;
|
|
3171
|
+
};
|
|
3172
|
+
const nodeIdOf = (el) => {
|
|
3173
|
+
for (const c of Array.from(el.classList))
|
|
3174
|
+
if (c.startsWith('iox-node-'))
|
|
3175
|
+
return c.slice('iox-node-'.length);
|
|
3176
|
+
return '';
|
|
3177
|
+
};
|
|
3178
|
+
function behaviourOf(root) {
|
|
3179
|
+
const on = root.getAttribute(ACTIVATE_ON_ATTRIBUTE);
|
|
3180
|
+
const orientation = root.getAttribute(ORIENTATION_ATTRIBUTE);
|
|
3181
|
+
return {
|
|
3182
|
+
activateOn: TABS_ACTIVATE_ONS.includes(on) ? on : TabsActivateOn.Click,
|
|
3183
|
+
orientation: TABS_ORIENTATIONS.includes(orientation) ? orientation : TabsOrientation.Horizontal,
|
|
3184
|
+
};
|
|
3185
|
+
}
|
|
3186
|
+
function setAttr(el, name, value) {
|
|
3187
|
+
if (!el)
|
|
3188
|
+
return;
|
|
3189
|
+
if (value === null) {
|
|
3190
|
+
if (el.hasAttribute(name))
|
|
3191
|
+
el.removeAttribute(name);
|
|
3192
|
+
}
|
|
3193
|
+
else if (el.getAttribute(name) !== value)
|
|
3194
|
+
el.setAttribute(name, value);
|
|
3195
|
+
}
|
|
3196
|
+
function dispatch(el, type) {
|
|
3197
|
+
const Ctor = globalThis.CustomEvent;
|
|
3198
|
+
if (el && Ctor)
|
|
3199
|
+
el.dispatchEvent(new Ctor(type, { bubbles: false }));
|
|
3200
|
+
}
|
|
3201
|
+
class ExclusiveRuntime {
|
|
3202
|
+
constructor(root, options = {}) {
|
|
3203
|
+
this.root = root;
|
|
3204
|
+
this.options = options;
|
|
3205
|
+
this.shown = null;
|
|
3206
|
+
this.observer = null;
|
|
3207
|
+
this.stampQueued = false;
|
|
3208
|
+
this.awaitingItems = false;
|
|
3209
|
+
this.onClick = (event) => {
|
|
3210
|
+
const trigger = this.ownTrigger(event.target);
|
|
3211
|
+
if (!trigger)
|
|
3212
|
+
return;
|
|
3213
|
+
event.preventDefault();
|
|
3214
|
+
this.activate(this.keyOf(trigger));
|
|
3215
|
+
};
|
|
3216
|
+
this.onHover = (event) => {
|
|
3217
|
+
if (this.behaviourValue.activateOn !== TabsActivateOn.Hover)
|
|
3218
|
+
return;
|
|
3219
|
+
const trigger = this.ownTrigger(event.target);
|
|
3220
|
+
if (trigger)
|
|
3221
|
+
this.activate(this.keyOf(trigger));
|
|
3222
|
+
};
|
|
3223
|
+
this.onKeydown = (event) => {
|
|
3224
|
+
const e = event;
|
|
3225
|
+
const trigger = this.ownTrigger(e.target);
|
|
3226
|
+
if (!trigger)
|
|
3227
|
+
return;
|
|
3228
|
+
const rtl = isRtl(trigger);
|
|
3229
|
+
const action = tabsKeyAction(e.key, this.behaviourValue.orientation, rtl);
|
|
3230
|
+
if (!action)
|
|
3231
|
+
return;
|
|
3232
|
+
const triggers = this.triggers();
|
|
3233
|
+
const target = triggers[accordionFocusIndex(action, triggers.indexOf(trigger), triggers.length)];
|
|
3234
|
+
if (!target)
|
|
3235
|
+
return;
|
|
3236
|
+
e.preventDefault();
|
|
3237
|
+
target.focus();
|
|
3238
|
+
this.activate(this.keyOf(target));
|
|
3239
|
+
};
|
|
3240
|
+
this.kind = kindOf(root);
|
|
3241
|
+
this.behaviourValue = options.behaviour ?? behaviourOf(root);
|
|
3242
|
+
root.setAttribute(COMPOSITE_ATTRIBUTE, this.kind);
|
|
3243
|
+
EXCLUSIVE_RUNTIMES.set(root, this);
|
|
3244
|
+
// The server rendered the first state (every trigger keyed, one on); with no server state the first is on.
|
|
3245
|
+
const triggers = this.triggers();
|
|
3246
|
+
const serverOn = !options.behaviour ? triggers.find(t => t.hasAttribute(ON_ATTRIBUTE) && t.hasAttribute(ITEM_KEY_ATTRIBUTE)) : undefined;
|
|
3247
|
+
this.live = serverOn ? this.keyOf(serverOn) : (triggers[0] ? this.keyOf(triggers[0]) : null);
|
|
3248
|
+
this.awaitingItems = !triggers.length;
|
|
3249
|
+
// Capture: on the builder canvas every part's own listener stops the click (it selects that part), and the tab
|
|
3250
|
+
// must still turn on — the canvas is a live runtime.
|
|
3251
|
+
root.addEventListener('click', this.onClick, true);
|
|
3252
|
+
root.addEventListener('mouseover', this.onHover, true);
|
|
3253
|
+
root.addEventListener('keydown', this.onKeydown);
|
|
3254
|
+
if (options.observe && typeof MutationObserver !== 'undefined') {
|
|
3255
|
+
this.observer = new MutationObserver(() => this.queueStamp());
|
|
3256
|
+
this.observer.observe(root, { childList: true, subtree: true });
|
|
3257
|
+
}
|
|
3258
|
+
this.stamp(true);
|
|
3259
|
+
}
|
|
3260
|
+
get behaviour() {
|
|
3261
|
+
return this.behaviourValue;
|
|
3262
|
+
}
|
|
3263
|
+
/** The behaviour changed in the panel (Activate on, Orientation). */
|
|
3264
|
+
setBehaviour(behaviour) {
|
|
3265
|
+
this.behaviourValue = behaviour;
|
|
3266
|
+
this.root.setAttribute(ACTIVATE_ON_ATTRIBUTE, behaviour.activateOn);
|
|
3267
|
+
this.root.setAttribute(ORIENTATION_ATTRIBUTE, behaviour.orientation);
|
|
3268
|
+
this.stamp();
|
|
3269
|
+
}
|
|
3270
|
+
/** THIS composite's triggers in document order (a nested composite's are its own). */
|
|
3271
|
+
triggers() {
|
|
3272
|
+
return this.own(TRIGGER_SELECTOR);
|
|
3273
|
+
}
|
|
3274
|
+
/** THIS composite's panels in document order. */
|
|
3275
|
+
panels() {
|
|
3276
|
+
return this.own(PANEL_SELECTOR);
|
|
3277
|
+
}
|
|
3278
|
+
/** The key on right now (forced ?? live). */
|
|
3279
|
+
get activeKey() {
|
|
3280
|
+
return this.shown;
|
|
3281
|
+
}
|
|
3282
|
+
/** The key the live state holds (before anything forced). */
|
|
3283
|
+
get liveKey() {
|
|
3284
|
+
return this.live;
|
|
3285
|
+
}
|
|
3286
|
+
/** The key of a trigger or a panel: its `data-iox-item-key`, else its index among its kind. */
|
|
3287
|
+
keyOf(part) {
|
|
3288
|
+
const own = part.getAttribute(ITEM_KEY_ATTRIBUTE);
|
|
3289
|
+
if (own)
|
|
3290
|
+
return own;
|
|
3291
|
+
const peers = part.matches(TRIGGER_SELECTOR) ? this.triggers() : this.panels();
|
|
3292
|
+
return `item-${peers.indexOf(part)}`;
|
|
3293
|
+
}
|
|
3294
|
+
/** The key of the item an element sits in (a part inside a tab or a panel), or null. */
|
|
3295
|
+
keyAt(el) {
|
|
3296
|
+
const part = closestExclusiveItemPart(el);
|
|
3297
|
+
return part && closestExclusive(part) === this.root ? this.keyOf(part) : null;
|
|
3298
|
+
}
|
|
3299
|
+
/** Turn `key` on (a click, a key, a hover, the `openItem` action, "Showing on canvas"). */
|
|
3300
|
+
activate(key) {
|
|
3301
|
+
if (!this.keys().includes(key))
|
|
3302
|
+
return;
|
|
3303
|
+
const changed = key !== this.live;
|
|
3304
|
+
this.live = key;
|
|
3305
|
+
this.stamp();
|
|
3306
|
+
if (changed)
|
|
3307
|
+
this.options.onChange?.(key);
|
|
3308
|
+
}
|
|
3309
|
+
/** Run an item action on `key`: Open turns it on; Close and Toggle do nothing (one item is always on). True when it
|
|
3310
|
+
* is on afterwards. */
|
|
3311
|
+
run(open, key) {
|
|
3312
|
+
if (open)
|
|
3313
|
+
this.activate(key);
|
|
3314
|
+
return this.shown === key;
|
|
3315
|
+
}
|
|
3316
|
+
/** The forced key changed (builder design state): show it. */
|
|
3317
|
+
refresh() {
|
|
3318
|
+
this.stamp();
|
|
3319
|
+
}
|
|
3320
|
+
destroy() {
|
|
3321
|
+
if (EXCLUSIVE_RUNTIMES.get(this.root) === this)
|
|
3322
|
+
EXCLUSIVE_RUNTIMES.delete(this.root);
|
|
3323
|
+
this.root.removeEventListener('click', this.onClick, true);
|
|
3324
|
+
this.root.removeEventListener('mouseover', this.onHover, true);
|
|
3325
|
+
this.root.removeEventListener('keydown', this.onKeydown);
|
|
3326
|
+
this.observer?.disconnect();
|
|
3327
|
+
this.observer = null;
|
|
3328
|
+
}
|
|
3329
|
+
keys() {
|
|
3330
|
+
return this.triggers().map(t => this.keyOf(t));
|
|
3331
|
+
}
|
|
3332
|
+
/** The element(s) that ARE the tablist: the ones a renderer already gave the role (the engine's component-path
|
|
3333
|
+
* Repeater puts it on its inner root, which holds the tabs), else the elements carrying the list's role class. */
|
|
3334
|
+
lists() {
|
|
3335
|
+
const stamped = this.own(TABLIST_SELECTOR);
|
|
3336
|
+
return stamped.length ? stamped : this.own(LIST_SELECTOR);
|
|
3337
|
+
}
|
|
3338
|
+
own(selector) {
|
|
3339
|
+
return Array.from(this.root.querySelectorAll(selector)).filter(el => closestExclusive(el.parentElement) === this.root);
|
|
3340
|
+
}
|
|
3341
|
+
queueStamp() {
|
|
3342
|
+
if (this.stampQueued)
|
|
3343
|
+
return;
|
|
3344
|
+
this.stampQueued = true;
|
|
3345
|
+
const run = () => { this.stampQueued = false; this.stamp(); };
|
|
3346
|
+
if (typeof queueMicrotask === 'function')
|
|
3347
|
+
queueMicrotask(run);
|
|
3348
|
+
else
|
|
3349
|
+
Promise.resolve().then(run);
|
|
3350
|
+
}
|
|
3351
|
+
rootId() {
|
|
3352
|
+
const own = this.options.rootId;
|
|
3353
|
+
return (typeof own === 'function' ? own() : own) ?? this.root.getAttribute(SCOPE_ATTRIBUTE) ?? nodeIdOf(this.root);
|
|
3354
|
+
}
|
|
3355
|
+
/** Write every part's state into its markup; announce what changed. */
|
|
3356
|
+
stamp(first = false) {
|
|
3357
|
+
const triggers = this.triggers();
|
|
3358
|
+
const keys = triggers.map(t => this.keyOf(t));
|
|
3359
|
+
if (this.awaitingItems && keys.length) {
|
|
3360
|
+
this.awaitingItems = false;
|
|
3361
|
+
this.live = keys[0];
|
|
3362
|
+
first = true;
|
|
3363
|
+
}
|
|
3364
|
+
if (this.live === null || !keys.includes(this.live))
|
|
3365
|
+
this.live = keys[0] ?? null;
|
|
3366
|
+
const forced = this.options.forced?.() ?? null;
|
|
3367
|
+
const next = forced !== null && keys.includes(forced) ? forced : this.live;
|
|
3368
|
+
const rootId = this.rootId();
|
|
3369
|
+
const vertical = this.behaviourValue.orientation === TabsOrientation.Vertical;
|
|
3370
|
+
for (const list of this.lists()) {
|
|
3371
|
+
setAttr(list, 'role', 'tablist');
|
|
3372
|
+
setAttr(list, 'aria-orientation', vertical ? 'vertical' : null);
|
|
3373
|
+
if (this.options.label)
|
|
3374
|
+
setAttr(list, 'aria-label', this.options.label() || null);
|
|
3375
|
+
}
|
|
3376
|
+
const panels = this.panels();
|
|
3377
|
+
const panelOf = (key) => panels.find(p => this.keyOf(p) === key) ?? null;
|
|
3378
|
+
for (const trigger of triggers) {
|
|
3379
|
+
const key = this.keyOf(trigger);
|
|
3380
|
+
const on = key === next;
|
|
3381
|
+
const ids = accordionDomIds(rootId, key, CompositeKind.Tabs);
|
|
3382
|
+
setAttr(trigger, ITEM_KEY_ATTRIBUTE, key);
|
|
3383
|
+
setAttr(trigger, 'type', trigger.tagName === 'BUTTON' ? 'button' : null);
|
|
3384
|
+
setAttr(trigger, 'role', 'tab');
|
|
3385
|
+
setAttr(trigger, 'id', ids.trigger);
|
|
3386
|
+
setAttr(trigger, 'aria-selected', on ? 'true' : 'false');
|
|
3387
|
+
setAttr(trigger, 'aria-controls', panelOf(key) ? ids.panel : null);
|
|
3388
|
+
setAttr(trigger, 'tabindex', on ? '0' : '-1');
|
|
3389
|
+
setAttr(trigger, ON_ATTRIBUTE, on ? '' : null);
|
|
3390
|
+
}
|
|
3391
|
+
for (const panel of panels) {
|
|
3392
|
+
const key = this.keyOf(panel);
|
|
3393
|
+
const on = key === next;
|
|
3394
|
+
const ids = accordionDomIds(rootId, key, CompositeKind.Tabs);
|
|
3395
|
+
setAttr(panel, ITEM_KEY_ATTRIBUTE, key);
|
|
3396
|
+
setAttr(panel, 'role', 'tabpanel');
|
|
3397
|
+
setAttr(panel, 'id', ids.panel);
|
|
3398
|
+
setAttr(panel, 'aria-labelledby', ids.trigger);
|
|
3399
|
+
setAttr(panel, 'tabindex', '0');
|
|
3400
|
+
setAttr(panel, ON_ATTRIBUTE, on ? '' : null);
|
|
3401
|
+
setAttr(panel, 'hidden', on ? null : '');
|
|
3402
|
+
}
|
|
3403
|
+
const was = first ? null : this.shown;
|
|
3404
|
+
this.shown = next;
|
|
3405
|
+
if (was === next && !first)
|
|
3406
|
+
return;
|
|
3407
|
+
if (was !== null) {
|
|
3408
|
+
dispatch(triggers.find(t => this.keyOf(t) === was), ITEM_OFF_EVENT);
|
|
3409
|
+
dispatch(panelOf(was), ITEM_OFF_EVENT);
|
|
3410
|
+
}
|
|
3411
|
+
if (next !== null) {
|
|
3412
|
+
dispatch(triggers.find(t => this.keyOf(t) === next), ITEM_ON_EVENT);
|
|
3413
|
+
dispatch(panelOf(next), ITEM_ON_EVENT);
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
ownTrigger(target) {
|
|
3417
|
+
const trigger = target?.closest?.(TRIGGER_SELECTOR);
|
|
3418
|
+
return trigger && closestExclusive(trigger.parentElement) === this.root ? trigger : null;
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
/** The direction the tab is laid out in — the computed `direction` (a page's own CSS included), else its nearest `dir`. */
|
|
3422
|
+
function isRtl(el) {
|
|
3423
|
+
let computed = '';
|
|
3424
|
+
try {
|
|
3425
|
+
computed = el.ownerDocument?.defaultView?.getComputedStyle(el).direction ?? '';
|
|
3426
|
+
}
|
|
3427
|
+
catch {
|
|
3428
|
+
computed = '';
|
|
3429
|
+
}
|
|
3430
|
+
if (computed)
|
|
3431
|
+
return computed === 'rtl';
|
|
3432
|
+
return el.closest('[dir]')?.getAttribute('dir') === 'rtl';
|
|
3433
|
+
}
|
|
3434
|
+
/** Every live exclusive runtime by its root — so an item action finds the runtime of whatever item it targets. */
|
|
3435
|
+
const EXCLUSIVE_RUNTIMES = new WeakMap();
|
|
3436
|
+
/** The runtime running an exclusive root, or null. */
|
|
3437
|
+
function exclusiveRuntimeOf(root) {
|
|
3438
|
+
return (root && EXCLUSIVE_RUNTIMES.get(root)) || null;
|
|
3439
|
+
}
|
|
3440
|
+
/** Attach the runtime to one rendered exclusive root, unless it already runs one. Returns the NEW runtime, or null. */
|
|
3441
|
+
function attachExclusiveAt(root, options = {}) {
|
|
3442
|
+
if (EXCLUSIVE_RUNTIMES.has(root))
|
|
3443
|
+
return null;
|
|
3444
|
+
return new ExclusiveRuntime(root, options);
|
|
3445
|
+
}
|
|
3446
|
+
/** True when `el` is (or sits in) an exclusive item part — a tab or a panel. */
|
|
3447
|
+
function isExclusiveItemOn(el) {
|
|
3448
|
+
return !!closestExclusiveItemPart(el)?.hasAttribute(ON_ATTRIBUTE);
|
|
3449
|
+
}
|
|
3450
|
+
/** Subscribe to the item part `el` sits in turning on / off. Returns the unsubscribe. */
|
|
3451
|
+
function onExclusiveItemState(el, on, handler) {
|
|
3452
|
+
const part = closestExclusiveItemPart(el);
|
|
3453
|
+
if (!part)
|
|
3454
|
+
return () => undefined;
|
|
3455
|
+
const type = on ? ITEM_ON_EVENT : ITEM_OFF_EVENT;
|
|
3456
|
+
part.addEventListener(type, handler);
|
|
3457
|
+
return () => part.removeEventListener(type, handler);
|
|
3458
|
+
}
|
|
3459
|
+
|
|
3460
|
+
/** Attach the runtime a rendered composite root needs (its `data-iox-composite`), unless it runs one already. Returns
|
|
3461
|
+
* the NEW runtime, or null. */
|
|
3462
|
+
function attachCompositeAt(root, options = {}) {
|
|
3463
|
+
const kind = root.getAttribute(COMPOSITE_ATTRIBUTE);
|
|
3464
|
+
if (isExclusiveKind(kind))
|
|
3465
|
+
return attachExclusiveAt(root, { observe: options.observe });
|
|
3466
|
+
return attachAccordionAt(root, { observe: options.observe, reducedMotion: options.reducedMotion });
|
|
3467
|
+
}
|
|
3468
|
+
/** The nearer of the item elements `el` sits in — an accordion item / a disclosure, or a tab / a tab panel. */
|
|
3469
|
+
function nearestItem(el) {
|
|
3470
|
+
const toggle = closestAccordionItem(el);
|
|
3471
|
+
const exclusive = closestExclusiveItemPart(el);
|
|
3472
|
+
if (!toggle && !exclusive)
|
|
3473
|
+
return null;
|
|
3474
|
+
if (toggle && (!exclusive || exclusive.contains(toggle)))
|
|
3475
|
+
return { el: toggle, exclusive: false };
|
|
3476
|
+
return { el: exclusive, exclusive: true };
|
|
3477
|
+
}
|
|
3478
|
+
/** Subscribe to the item `el` sits in turning on / off — what the `itemOn` / `itemOff` interaction triggers listen to,
|
|
3479
|
+
* in both families. Returns the unsubscribe. */
|
|
3480
|
+
function onCompositeItemState(el, on, handler) {
|
|
3481
|
+
const item = nearestItem(el);
|
|
3482
|
+
if (!item)
|
|
3483
|
+
return () => undefined;
|
|
3484
|
+
return item.exclusive ? onExclusiveItemState(item.el, on, handler) : onAccordionItemState(item.el, on, handler);
|
|
3485
|
+
}
|
|
3486
|
+
/** True when the item `el` sits in is on now (an open accordion item, the active tab or its panel). */
|
|
3487
|
+
function isCompositeItemOn(el) {
|
|
3488
|
+
const item = nearestItem(el);
|
|
3489
|
+
if (!item)
|
|
3490
|
+
return false;
|
|
3491
|
+
return item.exclusive ? isExclusiveItemOn(item.el) : isAccordionItemOn(item.el);
|
|
3492
|
+
}
|
|
3493
|
+
/**
|
|
3494
|
+
* Run an item action on the element of an ITEM — an accordion item, a disclosure, or a tab (CC4a: `openItem` turns it
|
|
3495
|
+
* on; a Tabs always shows one, so Close and Toggle leave it) — the `openItem` / `closeItem` / `toggleItem` interaction
|
|
3496
|
+
* actions, on the canvas and on the live site through this one function. Returns whether the item shows on
|
|
3497
|
+
* afterwards, or null when `el` is in no running composite.
|
|
3498
|
+
*/
|
|
3499
|
+
function runCompositeItemAction(el, action) {
|
|
3500
|
+
const item = nearestItem(el);
|
|
3501
|
+
if (!item)
|
|
3502
|
+
return null;
|
|
3503
|
+
if (item.exclusive) {
|
|
3504
|
+
const runtime = exclusiveRuntimeOf(closestExclusive(item.el));
|
|
3505
|
+
return runtime ? runtime.run(action === CompositeItemAction.Open, runtime.keyOf(item.el)) : null;
|
|
3506
|
+
}
|
|
3507
|
+
const runtime = accordionRuntimeOf(accordionRootOfItem(item.el));
|
|
3508
|
+
return runtime ? runtime.run(action, runtime.keyOf(item.el)) : null;
|
|
3509
|
+
}
|
|
3510
|
+
|
|
2528
3511
|
/**
|
|
2529
3512
|
* @vectoriox/iox-ui/composite-core — composites built from the builder's own parts (composite-components
|
|
2530
3513
|
* plan §3a). FRAMEWORK-FREE: the builder, the SSR engine and iox-cms (generation-core's composer, the site
|
|
2531
3514
|
* build) read the same recipes, roles and structure rules. Card v2 + Badge / Chip (T23 / T25) are the
|
|
2532
3515
|
* first; the Accordion (CC2) adds the first composite RUNTIME (accordion-runtime.ts — DOM only, no
|
|
2533
|
-
* framework), which the Disclosure (CC2.1) runs too — a one-item accordion whose root is its item; Tabs
|
|
2534
|
-
*
|
|
3516
|
+
* framework), which the Disclosure (CC2.1) runs too — a one-item accordion whose root is its item; Tabs (CC4a) run the
|
|
3517
|
+
* EXCLUSIVE runtime (exclusive-runtime.ts — one item always on); composite-runtime.ts is the one door to both.
|
|
2535
3518
|
* ⚠️ Consumers wire the usual trio — ng-package raw-ts assets, jest moduleNameMapper, tsconfig paths.
|
|
2536
3519
|
*/
|
|
2537
3520
|
|
|
@@ -2539,5 +3522,5 @@ function rowIndexOf(el) {
|
|
|
2539
3522
|
* Generated bundle index. Do not edit.
|
|
2540
3523
|
*/
|
|
2541
3524
|
|
|
2542
|
-
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, 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, 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, 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, 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, attachCarouselA11y, badgeStyleId, badgeToneOf, buildAccordionItem, buildAccordionRecipe, buildBadge, buildCardRecipe, buildCarouselRecipe, buildChip, buildCompositeIcon, buildDisclosureRecipe, buildLightboxRecipe, buildSliderPart, canOpenAtClickedItem, cardPartsOf, cardRootOf, cardTitleOf, carouselOf, clearLayerStyles, closeAccordionItem, closestAccordion, closestAccordionItem, compositeItemOf, compositeItems, compositeKindOfRole, compositeRootOf, compositeRowKeys, compositeScopeId, detachAccordion, detectAccordionIcon, detectCardLayout, disclosureOpenByDefault, domSafeKey, findByRole, hasLabelledRegion, hasSliderParts, initialAccordionOpen, isAccordionFromContent, isAccordionItemOn, isAccordionPart, isAccordionRoot, isCardLinked, isCardRoot, isCompositeItem, isCompositePanel, isCompositeRoot, isCompositeTrigger, isDisclosurePart, isDisclosureRoot, isLockedPart, isPillNode, isSlider, isSliderPart, itemOpenTriggerIds, lightboxSliderOf, makeCompositePart, migrateSliderControls, moveAccordionItem, needsSliderMigration, nextAccordionState, nextItemKey, onAccordionItemState, openAccordionItem, parentInComposite, pickAccordionAutoBinds, readLayerStyle, readProp, readStyle, removeFromComposite, renameAccordionItem, repeatSourceOf, rowIndexOf, rowItemKey, rowSourceOf, runCompositeItemAction, sameRepeatSource, serializeDomAttributes, setAccordionBehaviour, setAccordionIcon, setBadgeTone, setCardLinked, setCardPart, setSliderFlag, shouldScrollToItem, slideIndexForRow, sliderLiveRegion, sliderPart, sliderPartElement, sliderPauseAttributes, sliderPauseGlyph, sliderSlides, sliderWords, splitSliderChildren, structureRefusal, syncSliderParts, walkComposite, wantedSliderParts, writeLayerStyles, writeProp, writeStyles };
|
|
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 };
|
|
2543
3526
|
//# sourceMappingURL=vectoriox-iox-ui-composite-core.mjs.map
|