@incursa/ui-kit 1.0.1 → 1.2.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.
Files changed (45) hide show
  1. package/AI-AGENT-INSTRUCTIONS.md +41 -28
  2. package/LLMS.txt +55 -41
  3. package/README.md +181 -68
  4. package/dist/inc-design-language.css +413 -239
  5. package/dist/inc-design-language.css.map +1 -1
  6. package/dist/inc-design-language.js +520 -0
  7. package/dist/inc-design-language.min.css +1 -1
  8. package/dist/inc-design-language.min.css.map +1 -1
  9. package/dist/web-components/README.md +92 -0
  10. package/dist/web-components/RUNTIME-NOTES.md +40 -0
  11. package/dist/web-components/base-element.js +193 -0
  12. package/dist/web-components/components/feedback.js +1074 -0
  13. package/dist/web-components/components/forms.js +979 -0
  14. package/dist/web-components/components/layout.js +408 -0
  15. package/dist/web-components/components/navigation.js +854 -0
  16. package/dist/web-components/components/overlays.js +634 -0
  17. package/dist/web-components/controllers/focus.js +101 -0
  18. package/dist/web-components/controllers/overlay.js +128 -0
  19. package/dist/web-components/controllers/selection.js +145 -0
  20. package/dist/web-components/controllers/theme.js +173 -0
  21. package/dist/web-components/index.js +886 -0
  22. package/dist/web-components/package.json +3 -0
  23. package/dist/web-components/registry.js +74 -0
  24. package/dist/web-components/shared.js +186 -0
  25. package/dist/web-components/style.css +6 -0
  26. package/package.json +11 -2
  27. package/src/inc-design-language.js +520 -0
  28. package/src/inc-design-language.scss +434 -246
  29. package/src/web-components/README.md +92 -0
  30. package/src/web-components/RUNTIME-NOTES.md +40 -0
  31. package/src/web-components/base-element.js +193 -0
  32. package/src/web-components/components/feedback.js +1074 -0
  33. package/src/web-components/components/forms.js +979 -0
  34. package/src/web-components/components/layout.js +408 -0
  35. package/src/web-components/components/navigation.js +854 -0
  36. package/src/web-components/components/overlays.js +634 -0
  37. package/src/web-components/controllers/focus.js +101 -0
  38. package/src/web-components/controllers/overlay.js +128 -0
  39. package/src/web-components/controllers/selection.js +145 -0
  40. package/src/web-components/controllers/theme.js +173 -0
  41. package/src/web-components/index.js +886 -0
  42. package/src/web-components/package.json +3 -0
  43. package/src/web-components/registry.js +74 -0
  44. package/src/web-components/shared.js +186 -0
  45. package/src/web-components/style.css +6 -0
@@ -0,0 +1,854 @@
1
+ "use strict";
2
+
3
+ const NAVBAR_TAG = "inc-navbar";
4
+ const TABS_TAG = "inc-tabs";
5
+ const USER_MENU_TAG = "inc-user-menu";
6
+
7
+ const TAB_KEYS = new Set(["ArrowRight", "ArrowLeft", "ArrowDown", "ArrowUp", "Home", "End", "Enter", " "]);
8
+ const MENU_KEYS = new Set(["ArrowDown", "ArrowUp", "Home", "End", "Escape", "Enter", " "]);
9
+ const HostElement = typeof HTMLElement === "undefined" ? class {} : HTMLElement;
10
+
11
+ let uidCounter = 0;
12
+
13
+ function nextId(prefix) {
14
+ uidCounter += 1;
15
+ return `${prefix}-${uidCounter}`;
16
+ }
17
+
18
+ function asBoolean(value) {
19
+ if (typeof value === "string") {
20
+ const normalized = value.trim().toLowerCase();
21
+
22
+ if (normalized === "false" || normalized === "0" || normalized === "off" || normalized === "no") {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ return Boolean(value);
28
+ }
29
+
30
+ function normalizeActivation(value) {
31
+ return value === "manual" ? "manual" : "auto";
32
+ }
33
+
34
+ function normalizeOrientation(value) {
35
+ return value === "vertical" ? "vertical" : "horizontal";
36
+ }
37
+
38
+ function emit(host, type, detail = {}) {
39
+ host.dispatchEvent(new CustomEvent(type, {
40
+ bubbles: true,
41
+ composed: true,
42
+ detail,
43
+ }));
44
+ }
45
+
46
+ function defineClassToken(element, token, on) {
47
+ if (!element || !token) {
48
+ return;
49
+ }
50
+
51
+ element.classList.toggle(token, Boolean(on));
52
+ }
53
+
54
+ function getFocusableItems(container) {
55
+ if (!(container instanceof HTMLElement)) {
56
+ return [];
57
+ }
58
+
59
+ return Array.from(container.querySelectorAll("a[href], button:not([disabled]), [role='menuitem'], [role='menuitemradio'], [role='menuitemcheckbox'], [tabindex]:not([tabindex='-1'])"))
60
+ .filter((candidate) => candidate instanceof HTMLElement && !candidate.hasAttribute("disabled") && !candidate.hasAttribute("aria-disabled"));
61
+ }
62
+
63
+ class IncNavbarElement extends HostElement {
64
+ static get observedAttributes() {
65
+ return ["expanded", "breakpoint", "app", "variant"];
66
+ }
67
+
68
+ constructor() {
69
+ super();
70
+ this._boundClick = (event) => this._onClick(event);
71
+ this._boundKeydown = (event) => this._onKeydown(event);
72
+ this._boundSlotChange = () => this._syncStructure();
73
+ }
74
+
75
+ connectedCallback() {
76
+ this.classList.add("inc-navbar");
77
+ this.setAttribute("role", this.getAttribute("role") || "navigation");
78
+ this._syncStructure();
79
+ this._syncClasses();
80
+ this.addEventListener("click", this._boundClick);
81
+ this.addEventListener("keydown", this._boundKeydown);
82
+ this.addEventListener("slotchange", this._boundSlotChange);
83
+ }
84
+
85
+ disconnectedCallback() {
86
+ this.removeEventListener("click", this._boundClick);
87
+ this.removeEventListener("keydown", this._boundKeydown);
88
+ this.removeEventListener("slotchange", this._boundSlotChange);
89
+ }
90
+
91
+ attributeChangedCallback(name) {
92
+ if (name === "expanded") {
93
+ emit(this, "toggle", { expanded: this.expanded });
94
+ }
95
+
96
+ this._syncClasses();
97
+ }
98
+
99
+ get expanded() {
100
+ return this.hasAttribute("expanded");
101
+ }
102
+
103
+ set expanded(value) {
104
+ if (asBoolean(value)) {
105
+ this.setAttribute("expanded", "");
106
+ } else {
107
+ this.removeAttribute("expanded");
108
+ }
109
+ }
110
+
111
+ expand() {
112
+ if (!this.expanded) {
113
+ this.expanded = true;
114
+ emit(this, "open", { expanded: true });
115
+ }
116
+ }
117
+
118
+ collapse() {
119
+ if (this.expanded) {
120
+ this.expanded = false;
121
+ emit(this, "close", { expanded: false });
122
+ }
123
+ }
124
+
125
+ toggle() {
126
+ if (this.expanded) {
127
+ this.collapse();
128
+ return false;
129
+ }
130
+
131
+ this.expand();
132
+ return true;
133
+ }
134
+
135
+ _syncClasses() {
136
+ defineClassToken(this, "inc-navbar--app", this.hasAttribute("app"));
137
+
138
+ const breakpoint = this.getAttribute("breakpoint");
139
+ Array.from(this.classList)
140
+ .filter((token) => token.startsWith("inc-navbar--expand-"))
141
+ .forEach((token) => this.classList.remove(token));
142
+
143
+ if (breakpoint) {
144
+ this.classList.add(`inc-navbar--expand-${breakpoint}`);
145
+ }
146
+
147
+ const variant = this.getAttribute("variant");
148
+ Array.from(this.classList)
149
+ .filter((token) => token.startsWith("inc-navbar--variant-"))
150
+ .forEach((token) => this.classList.remove(token));
151
+
152
+ if (variant) {
153
+ this.classList.add(`inc-navbar--variant-${variant}`);
154
+ }
155
+
156
+ this.setAttribute("aria-expanded", this.expanded ? "true" : "false");
157
+ }
158
+
159
+ _syncStructure() {
160
+ this.querySelectorAll(":scope > [slot='brand']").forEach((node) => node.classList.add("inc-navbar__brand"));
161
+ this.querySelectorAll(":scope > [slot='nav']").forEach((node) => node.classList.add("inc-navbar__nav"));
162
+ this.querySelectorAll(":scope > [slot='utilities']").forEach((node) => node.classList.add("inc-navbar__utilities"));
163
+ this.querySelectorAll(":scope > [slot='collapse']").forEach((node) => node.classList.add("inc-navbar__collapse"));
164
+ }
165
+
166
+ _onClick(event) {
167
+ const toggle = event.target instanceof Element ? event.target.closest("[data-inc-navbar-toggle]") : null;
168
+
169
+ if (!toggle || !this.contains(toggle)) {
170
+ return;
171
+ }
172
+
173
+ event.preventDefault();
174
+ this.toggle();
175
+ }
176
+
177
+ _onKeydown(event) {
178
+ if (event.key === "Escape" && this.expanded) {
179
+ this.collapse();
180
+ }
181
+ }
182
+ }
183
+
184
+ class IncTabsElement extends HostElement {
185
+ static get observedAttributes() {
186
+ return ["selected", "orientation", "activation", "variant", "fill", "justified"];
187
+ }
188
+
189
+ constructor() {
190
+ super();
191
+ this._boundClick = (event) => this._onClick(event);
192
+ this._boundKeydown = (event) => this._onKeydown(event);
193
+ this._boundSlotChange = () => this._initialize();
194
+ }
195
+
196
+ connectedCallback() {
197
+ this.classList.add("inc-tabs-host");
198
+ this.addEventListener("click", this._boundClick);
199
+ this.addEventListener("keydown", this._boundKeydown);
200
+ this.addEventListener("slotchange", this._boundSlotChange);
201
+ this._initialize();
202
+ }
203
+
204
+ disconnectedCallback() {
205
+ this.removeEventListener("click", this._boundClick);
206
+ this.removeEventListener("keydown", this._boundKeydown);
207
+ this.removeEventListener("slotchange", this._boundSlotChange);
208
+ }
209
+
210
+ attributeChangedCallback(name) {
211
+ if (name === "selected") {
212
+ this.select(this.getAttribute("selected"), { emitEvents: false, focus: false });
213
+ return;
214
+ }
215
+
216
+ this._syncHostClasses();
217
+ this._syncTabs();
218
+ }
219
+
220
+ get selected() {
221
+ return this.getAttribute("selected");
222
+ }
223
+
224
+ set selected(value) {
225
+ if (value === null || value === undefined || value === "") {
226
+ this.removeAttribute("selected");
227
+ return;
228
+ }
229
+
230
+ this.setAttribute("selected", String(value));
231
+ }
232
+
233
+ select(value, options = {}) {
234
+ const tabs = this._tabs();
235
+ const panels = this._panels();
236
+ const target = this._resolveTab(value, tabs);
237
+
238
+ if (!target) {
239
+ return false;
240
+ }
241
+
242
+ const previous = tabs.find((tab) => tab.getAttribute("aria-selected") === "true") || null;
243
+ const previousId = previous?.id || null;
244
+ const nextId = target.id || null;
245
+
246
+ if (previous === target && options.force !== true) {
247
+ if (options.focus) {
248
+ target.focus();
249
+ }
250
+
251
+ return true;
252
+ }
253
+
254
+ tabs.forEach((tab, index) => {
255
+ const isActive = tab === target;
256
+ const panel = this._resolvePanel(tab, panels, index);
257
+
258
+ tab.classList.toggle("active", isActive);
259
+ tab.setAttribute("aria-selected", isActive ? "true" : "false");
260
+ tab.tabIndex = isActive ? 0 : -1;
261
+
262
+ if (panel) {
263
+ panel.hidden = !isActive;
264
+ panel.classList.toggle("active", isActive);
265
+ panel.classList.toggle("show", isActive);
266
+ }
267
+ });
268
+
269
+ if (nextId) {
270
+ this.setAttribute("selected", nextId);
271
+ }
272
+
273
+ if (options.focus) {
274
+ target.focus();
275
+ }
276
+
277
+ if (options.emitEvents !== false) {
278
+ emit(this, "select", { previous: previousId, selected: nextId, tab: target });
279
+ emit(this, "change", { previous: previousId, selected: nextId, tab: target });
280
+ }
281
+
282
+ return true;
283
+ }
284
+
285
+ next() {
286
+ return this._stepSelection(1);
287
+ }
288
+
289
+ previous() {
290
+ return this._stepSelection(-1);
291
+ }
292
+
293
+ _initialize() {
294
+ this._syncHostClasses();
295
+ this._syncTabs();
296
+
297
+ const selected = this.getAttribute("selected");
298
+
299
+ if (selected) {
300
+ this.select(selected, { emitEvents: false, focus: false, force: true });
301
+ return;
302
+ }
303
+
304
+ const tabs = this._tabs();
305
+ const active = tabs.find((tab) => tab.classList.contains("active")) || tabs[0];
306
+
307
+ if (active) {
308
+ this.select(active.id, { emitEvents: false, focus: false, force: true });
309
+ }
310
+ }
311
+
312
+ _syncHostClasses() {
313
+ const orientation = normalizeOrientation(this.getAttribute("orientation"));
314
+ this.setAttribute("data-inc-tabs-orientation", orientation);
315
+
316
+ const activation = normalizeActivation(this.getAttribute("activation"));
317
+ this.setAttribute("data-inc-tabs-activation", activation);
318
+
319
+ const variant = this.getAttribute("variant");
320
+ Array.from(this.classList)
321
+ .filter((token) => token.startsWith("inc-tabs-host--"))
322
+ .forEach((token) => this.classList.remove(token));
323
+
324
+ if (variant) {
325
+ this.classList.add(`inc-tabs-host--${variant}`);
326
+ }
327
+
328
+ defineClassToken(this, "inc-tabs-host--fill", this.hasAttribute("fill"));
329
+ defineClassToken(this, "inc-tabs-host--justified", this.hasAttribute("justified"));
330
+ }
331
+
332
+ _syncTabs() {
333
+ const tabs = this._tabs();
334
+ const panels = this._panels();
335
+ const orientation = normalizeOrientation(this.getAttribute("orientation"));
336
+ const roleRoot = this.querySelector("[role='tablist'], .inc-tabs-nav");
337
+
338
+ if (roleRoot instanceof HTMLElement) {
339
+ roleRoot.setAttribute("role", "tablist");
340
+ roleRoot.setAttribute("aria-orientation", orientation);
341
+ if (!roleRoot.classList.contains("inc-tabs-nav")) {
342
+ roleRoot.classList.add("inc-tabs-nav");
343
+ }
344
+ }
345
+
346
+ tabs.forEach((tab, index) => {
347
+ const panel = this._resolvePanel(tab, panels, index);
348
+
349
+ if (!tab.id) {
350
+ tab.id = nextId("inc-tab");
351
+ }
352
+
353
+ tab.setAttribute("role", "tab");
354
+ if (!tab.hasAttribute("tabindex")) {
355
+ tab.tabIndex = index === 0 ? 0 : -1;
356
+ }
357
+
358
+ if (panel && !panel.id) {
359
+ panel.id = nextId("inc-tab-panel");
360
+ }
361
+
362
+ if (panel) {
363
+ tab.setAttribute("aria-controls", panel.id);
364
+ panel.setAttribute("role", "tabpanel");
365
+ panel.setAttribute("aria-labelledby", tab.id);
366
+ }
367
+ });
368
+ }
369
+
370
+ _tabs() {
371
+ const explicit = Array.from(this.querySelectorAll(":scope > .inc-tabs-nav > li > *"));
372
+ const unique = [];
373
+
374
+ explicit.forEach((candidate) => {
375
+ if (!(candidate instanceof HTMLElement)) {
376
+ return;
377
+ }
378
+
379
+ if (!unique.includes(candidate)) {
380
+ unique.push(candidate);
381
+ }
382
+ });
383
+
384
+ return unique;
385
+ }
386
+
387
+ _panels() {
388
+ const explicit = Array.from(this.querySelectorAll(":scope > [slot='panel'], [data-inc-tab-panel], .inc-tab-pane, [role='tabpanel']"));
389
+ const unique = [];
390
+
391
+ explicit.forEach((candidate) => {
392
+ if (!(candidate instanceof HTMLElement)) {
393
+ return;
394
+ }
395
+
396
+ if (!unique.includes(candidate)) {
397
+ unique.push(candidate);
398
+ }
399
+ });
400
+
401
+ return unique;
402
+ }
403
+
404
+ _resolveTab(value, tabs) {
405
+ if (!tabs.length) {
406
+ return null;
407
+ }
408
+
409
+ if (value === null || value === undefined || value === "") {
410
+ return tabs[0];
411
+ }
412
+
413
+ if (typeof value === "number") {
414
+ return tabs[value] || null;
415
+ }
416
+
417
+ const raw = String(value);
418
+ const noHash = raw.startsWith("#") ? raw.slice(1) : raw;
419
+ const byId = tabs.find((tab) => tab.id === noHash);
420
+ if (byId) {
421
+ return byId;
422
+ }
423
+
424
+ const asNumber = Number.parseInt(raw, 10);
425
+ if (Number.isFinite(asNumber)) {
426
+ return tabs[asNumber] || null;
427
+ }
428
+
429
+ return tabs.find((tab) => tab.getAttribute("aria-controls") === noHash) || null;
430
+ }
431
+
432
+ _resolvePanel(tab, panels, fallbackIndex) {
433
+ if (!(tab instanceof HTMLElement)) {
434
+ return null;
435
+ }
436
+
437
+ const ariaControls = tab.getAttribute("aria-controls");
438
+ if (ariaControls) {
439
+ const escapedId = typeof CSS !== "undefined" && typeof CSS.escape === "function"
440
+ ? CSS.escape(ariaControls)
441
+ : ariaControls.replace(/([^\w-])/g, "\\$1");
442
+ const direct = this.querySelector(`#${escapedId}`);
443
+ if (direct instanceof HTMLElement) {
444
+ return direct;
445
+ }
446
+ }
447
+
448
+ const href = tab.getAttribute("href");
449
+ if (href && href.startsWith("#")) {
450
+ const fromHref = this.querySelector(href);
451
+ if (fromHref instanceof HTMLElement) {
452
+ return fromHref;
453
+ }
454
+ }
455
+
456
+ const target = tab.getAttribute("data-inc-target");
457
+ if (target) {
458
+ try {
459
+ const fromTarget = this.querySelector(target);
460
+ if (fromTarget instanceof HTMLElement) {
461
+ return fromTarget;
462
+ }
463
+ } catch {
464
+ // Invalid selector is ignored intentionally.
465
+ }
466
+ }
467
+
468
+ return panels[fallbackIndex] || null;
469
+ }
470
+
471
+ _stepSelection(delta) {
472
+ const tabs = this._tabs();
473
+ if (!tabs.length) {
474
+ return false;
475
+ }
476
+
477
+ const activeIndex = Math.max(0, tabs.findIndex((tab) => tab.getAttribute("aria-selected") === "true"));
478
+ const nextIndex = (activeIndex + delta + tabs.length) % tabs.length;
479
+ return this.select(tabs[nextIndex].id, { focus: true });
480
+ }
481
+
482
+ _onClick(event) {
483
+ const tab = event.target instanceof Element ? event.target.closest("[slot='tab'], [data-inc-tab], [role='tab']") : null;
484
+ if (!(tab instanceof HTMLElement) || !this.contains(tab)) {
485
+ return;
486
+ }
487
+
488
+ if (tab.tagName === "A") {
489
+ event.preventDefault();
490
+ }
491
+
492
+ this.select(tab.id || tab.getAttribute("aria-controls") || "", { focus: true });
493
+ }
494
+
495
+ _onKeydown(event) {
496
+ if (!TAB_KEYS.has(event.key)) {
497
+ return;
498
+ }
499
+
500
+ const tab = event.target instanceof Element ? event.target.closest("[slot='tab'], [data-inc-tab], [role='tab']") : null;
501
+ if (!(tab instanceof HTMLElement) || !this.contains(tab)) {
502
+ return;
503
+ }
504
+
505
+ const tabs = this._tabs();
506
+ const currentIndex = tabs.indexOf(tab);
507
+ if (currentIndex < 0) {
508
+ return;
509
+ }
510
+
511
+ const orientation = normalizeOrientation(this.getAttribute("orientation"));
512
+ const activation = normalizeActivation(this.getAttribute("activation"));
513
+ let nextIndex = currentIndex;
514
+
515
+ if (event.key === "Home") {
516
+ nextIndex = 0;
517
+ } else if (event.key === "End") {
518
+ nextIndex = tabs.length - 1;
519
+ } else if ((event.key === "ArrowRight" && orientation === "horizontal") || (event.key === "ArrowDown" && orientation === "vertical")) {
520
+ nextIndex = (currentIndex + 1) % tabs.length;
521
+ } else if ((event.key === "ArrowLeft" && orientation === "horizontal") || (event.key === "ArrowUp" && orientation === "vertical")) {
522
+ nextIndex = (currentIndex - 1 + tabs.length) % tabs.length;
523
+ } else if (event.key === "Enter" || event.key === " ") {
524
+ event.preventDefault();
525
+ this.select(tab.id, { focus: true });
526
+ return;
527
+ } else {
528
+ return;
529
+ }
530
+
531
+ event.preventDefault();
532
+ const nextTab = tabs[nextIndex];
533
+ nextTab.focus();
534
+
535
+ if (activation === "auto") {
536
+ this.select(nextTab.id, { focus: false });
537
+ }
538
+ }
539
+ }
540
+
541
+ class IncUserMenuElement extends HostElement {
542
+ static get observedAttributes() {
543
+ return ["open", "label", "placement"];
544
+ }
545
+
546
+ constructor() {
547
+ super();
548
+ this._boundClick = (event) => this._onClick(event);
549
+ this._boundKeydown = (event) => this._onKeydown(event);
550
+ this._boundPointerDown = (event) => this._onPointerDown(event);
551
+ this._boundSlotChange = () => this._syncStructure();
552
+ }
553
+
554
+ connectedCallback() {
555
+ this.classList.add("inc-native-menu", "inc-user-menu");
556
+ this._syncStructure();
557
+ this._syncState();
558
+ this.addEventListener("click", this._boundClick);
559
+ this.addEventListener("keydown", this._boundKeydown);
560
+ this.addEventListener("slotchange", this._boundSlotChange);
561
+ document.addEventListener("pointerdown", this._boundPointerDown, true);
562
+ }
563
+
564
+ disconnectedCallback() {
565
+ this.removeEventListener("click", this._boundClick);
566
+ this.removeEventListener("keydown", this._boundKeydown);
567
+ this.removeEventListener("slotchange", this._boundSlotChange);
568
+ document.removeEventListener("pointerdown", this._boundPointerDown, true);
569
+ }
570
+
571
+ attributeChangedCallback() {
572
+ this._syncState();
573
+ }
574
+
575
+ open() {
576
+ if (!this.hasAttribute("open")) {
577
+ this.setAttribute("open", "");
578
+ emit(this, "open", { open: true });
579
+ }
580
+ }
581
+
582
+ close({ restoreFocus = false } = {}) {
583
+ if (this.hasAttribute("open")) {
584
+ this.removeAttribute("open");
585
+ emit(this, "close", { open: false });
586
+ }
587
+
588
+ if (restoreFocus) {
589
+ this._trigger()?.focus();
590
+ }
591
+ }
592
+
593
+ toggle() {
594
+ if (this.hasAttribute("open")) {
595
+ this.close();
596
+ return false;
597
+ }
598
+
599
+ this.open();
600
+ return true;
601
+ }
602
+
603
+ _trigger() {
604
+ return this.querySelector(":scope > [slot='trigger'], :scope > .inc-native-menu__summary");
605
+ }
606
+
607
+ _menu() {
608
+ return this.querySelector(":scope > [slot='menu'], :scope > .inc-native-menu__panel");
609
+ }
610
+
611
+ _items() {
612
+ const menu = this._menu();
613
+ if (!(menu instanceof HTMLElement)) {
614
+ return [];
615
+ }
616
+
617
+ return getFocusableItems(menu).filter((item) => menu.contains(item));
618
+ }
619
+
620
+ _syncStructure() {
621
+ const trigger = this._trigger();
622
+ const menu = this._menu();
623
+
624
+ if (trigger instanceof HTMLElement) {
625
+ trigger.classList.add("inc-native-menu__summary");
626
+ if (!trigger.id) {
627
+ trigger.id = nextId("inc-user-menu-trigger");
628
+ }
629
+ trigger.setAttribute("aria-haspopup", "menu");
630
+ }
631
+
632
+ if (menu instanceof HTMLElement) {
633
+ menu.classList.add("inc-native-menu__panel");
634
+ if (!menu.id) {
635
+ menu.id = nextId("inc-user-menu-panel");
636
+ }
637
+
638
+ menu.setAttribute("role", "menu");
639
+ menu.setAttribute("aria-label", this.getAttribute("label") || "User menu");
640
+ }
641
+
642
+ this.querySelectorAll(":scope > [slot='item']").forEach((item) => {
643
+ item.classList.add("inc-native-menu__item");
644
+ item.setAttribute("role", item.getAttribute("role") || "menuitem");
645
+ if (!item.hasAttribute("tabindex")) {
646
+ item.tabIndex = -1;
647
+ }
648
+ });
649
+
650
+ if (trigger instanceof HTMLElement && menu instanceof HTMLElement) {
651
+ trigger.setAttribute("aria-controls", menu.id);
652
+ }
653
+ }
654
+
655
+ _syncState() {
656
+ const trigger = this._trigger();
657
+ const menu = this._menu();
658
+ const isOpen = this.hasAttribute("open");
659
+
660
+ defineClassToken(this, "is-open", isOpen);
661
+
662
+ Array.from(this.classList)
663
+ .filter((token) => token.startsWith("inc-user-menu--"))
664
+ .forEach((token) => this.classList.remove(token));
665
+
666
+ const placement = this.getAttribute("placement");
667
+ if (placement) {
668
+ this.classList.add(`inc-user-menu--${placement}`);
669
+ }
670
+
671
+ if (trigger instanceof HTMLElement) {
672
+ trigger.setAttribute("aria-expanded", isOpen ? "true" : "false");
673
+ }
674
+
675
+ if (menu instanceof HTMLElement) {
676
+ menu.classList.toggle("show", isOpen);
677
+ menu.hidden = !isOpen;
678
+ }
679
+ }
680
+
681
+ _focusItem(direction) {
682
+ const items = this._items();
683
+ if (!items.length) {
684
+ return;
685
+ }
686
+
687
+ const active = document.activeElement instanceof HTMLElement ? document.activeElement : null;
688
+ const currentIndex = active ? items.indexOf(active) : -1;
689
+ let next = items[0];
690
+
691
+ if (direction === "last") {
692
+ next = items[items.length - 1];
693
+ } else if (direction === "next" && currentIndex >= 0) {
694
+ next = items[(currentIndex + 1) % items.length];
695
+ } else if (direction === "previous" && currentIndex >= 0) {
696
+ next = items[(currentIndex - 1 + items.length) % items.length];
697
+ } else if (direction === "previous" && currentIndex < 0) {
698
+ next = items[items.length - 1];
699
+ }
700
+
701
+ next.focus();
702
+ }
703
+
704
+ _onPointerDown(event) {
705
+ if (!(event.target instanceof Node)) {
706
+ return;
707
+ }
708
+
709
+ if (!this.contains(event.target)) {
710
+ this.close();
711
+ }
712
+ }
713
+
714
+ _onClick(event) {
715
+ const trigger = event.target instanceof Element ? event.target.closest("[slot='trigger'], .inc-native-menu__summary") : null;
716
+ if (trigger && this.contains(trigger)) {
717
+ event.preventDefault();
718
+
719
+ const openNow = this.toggle();
720
+ if (openNow) {
721
+ this._focusItem("first");
722
+ }
723
+ return;
724
+ }
725
+
726
+ const item = event.target instanceof Element ? event.target.closest("[slot='item'], .inc-native-menu__item, [role='menuitem']") : null;
727
+ if (!item || !this.contains(item)) {
728
+ return;
729
+ }
730
+
731
+ emit(this, "select", {
732
+ item,
733
+ value: item.getAttribute("value") || item.getAttribute("data-value") || item.textContent?.trim() || "",
734
+ text: item.textContent?.trim() || "",
735
+ });
736
+
737
+ this.close({ restoreFocus: true });
738
+ }
739
+
740
+ _onKeydown(event) {
741
+ if (!MENU_KEYS.has(event.key)) {
742
+ return;
743
+ }
744
+
745
+ const trigger = event.target instanceof Element ? event.target.closest("[slot='trigger'], .inc-native-menu__summary") : null;
746
+ const menu = event.target instanceof Element ? event.target.closest("[slot='menu'], .inc-native-menu__panel") : null;
747
+
748
+ if (trigger && this.contains(trigger)) {
749
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
750
+ event.preventDefault();
751
+ this.open();
752
+ this._focusItem(event.key === "ArrowDown" ? "first" : "last");
753
+ return;
754
+ }
755
+
756
+ if (event.key === "Enter" || event.key === " ") {
757
+ event.preventDefault();
758
+ const openNow = this.toggle();
759
+ if (openNow) {
760
+ this._focusItem("first");
761
+ }
762
+ return;
763
+ }
764
+
765
+ if (event.key === "Escape") {
766
+ event.preventDefault();
767
+ this.close({ restoreFocus: true });
768
+ }
769
+
770
+ return;
771
+ }
772
+
773
+ if (menu && this.contains(menu)) {
774
+ if (event.key === "ArrowDown") {
775
+ event.preventDefault();
776
+ this._focusItem("next");
777
+ return;
778
+ }
779
+
780
+ if (event.key === "ArrowUp") {
781
+ event.preventDefault();
782
+ this._focusItem("previous");
783
+ return;
784
+ }
785
+
786
+ if (event.key === "Home") {
787
+ event.preventDefault();
788
+ this._focusItem("first");
789
+ return;
790
+ }
791
+
792
+ if (event.key === "End") {
793
+ event.preventDefault();
794
+ this._focusItem("last");
795
+ return;
796
+ }
797
+
798
+ if (event.key === "Escape") {
799
+ event.preventDefault();
800
+ this.close({ restoreFocus: true });
801
+ }
802
+ }
803
+ }
804
+ }
805
+
806
+ function defineNavigationComponents(registry = globalThis.customElements) {
807
+ if (!registry) {
808
+ return {
809
+ navbarDefined: false,
810
+ tabsDefined: false,
811
+ userMenuDefined: false,
812
+ };
813
+ }
814
+
815
+ let navbarDefined = false;
816
+ let tabsDefined = false;
817
+ let userMenuDefined = false;
818
+
819
+ if (!registry.get(NAVBAR_TAG)) {
820
+ registry.define(NAVBAR_TAG, IncNavbarElement);
821
+ navbarDefined = true;
822
+ }
823
+
824
+ if (!registry.get(TABS_TAG)) {
825
+ registry.define(TABS_TAG, IncTabsElement);
826
+ tabsDefined = true;
827
+ }
828
+
829
+ if (!registry.get(USER_MENU_TAG)) {
830
+ registry.define(USER_MENU_TAG, IncUserMenuElement);
831
+ userMenuDefined = true;
832
+ }
833
+
834
+ return { navbarDefined, tabsDefined, userMenuDefined };
835
+ }
836
+
837
+ const navigationApi = {
838
+ NAVBAR_TAG,
839
+ TABS_TAG,
840
+ USER_MENU_TAG,
841
+ IncNavbarElement,
842
+ IncTabsElement,
843
+ IncUserMenuElement,
844
+ defineNavigationComponents,
845
+ };
846
+
847
+ if (typeof module !== "undefined" && module.exports) {
848
+ module.exports = navigationApi;
849
+ }
850
+
851
+ if (typeof window !== "undefined") {
852
+ window.IncWebComponents = window.IncWebComponents || {};
853
+ window.IncWebComponents.navigation = navigationApi;
854
+ }