@iris-ui-kit/vue 0.2.24 → 0.2.26

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 (57) hide show
  1. package/dist/admin.cjs +390 -170
  2. package/dist/admin.cjs.map +1 -1
  3. package/dist/admin.d.cts +3 -2
  4. package/dist/admin.d.ts +3 -2
  5. package/dist/admin.js +2 -2
  6. package/dist/behaviors.cjs +74 -8
  7. package/dist/behaviors.cjs.map +1 -1
  8. package/dist/behaviors.d.cts +57 -1
  9. package/dist/behaviors.d.ts +57 -1
  10. package/dist/behaviors.js +1 -1
  11. package/dist/{chunk-FSDQHBI2.js → chunk-3547J6IN.js} +2 -2
  12. package/dist/chunk-3547J6IN.js.map +1 -0
  13. package/dist/{chunk-WL67XCH5.js → chunk-IN3DQ55U.js} +2 -2
  14. package/dist/chunk-IN3DQ55U.js.map +1 -0
  15. package/dist/{chunk-3IH7IJ5S.js → chunk-IOMKC5OI.js} +78 -13
  16. package/dist/chunk-IOMKC5OI.js.map +1 -0
  17. package/dist/{chunk-3FJUCHCC.js → chunk-IPR5JAVF.js} +11 -3
  18. package/dist/chunk-IPR5JAVF.js.map +1 -0
  19. package/dist/{chunk-BEPH6PPL.js → chunk-LWABTQCB.js} +57 -37
  20. package/dist/chunk-LWABTQCB.js.map +1 -0
  21. package/dist/{chunk-DNRQVM3Q.js → chunk-QBFYIDY7.js} +3 -2
  22. package/dist/chunk-QBFYIDY7.js.map +1 -0
  23. package/dist/{chunk-QQ25RKOO.js → chunk-ZWFSGMOB.js} +385 -173
  24. package/dist/chunk-ZWFSGMOB.js.map +1 -0
  25. package/dist/data.cjs +1 -0
  26. package/dist/data.cjs.map +1 -1
  27. package/dist/data.d.cts +4 -2
  28. package/dist/data.d.ts +4 -2
  29. package/dist/data.js +1 -1
  30. package/dist/error-boundary.cjs.map +1 -1
  31. package/dist/error-boundary.js +1 -1
  32. package/dist/floating.cjs +9 -1
  33. package/dist/floating.cjs.map +1 -1
  34. package/dist/floating.d.cts +6 -0
  35. package/dist/floating.d.ts +6 -0
  36. package/dist/floating.js +1 -1
  37. package/dist/index.cjs +1933 -689
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +171 -7
  40. package/dist/index.d.ts +171 -7
  41. package/dist/index.js +1413 -476
  42. package/dist/index.js.map +1 -1
  43. package/dist/layouts.cjs.map +1 -1
  44. package/dist/layouts.d.cts +1 -1
  45. package/dist/layouts.d.ts +1 -1
  46. package/dist/layouts.js +1 -1
  47. package/dist/skeletons.cjs +55 -35
  48. package/dist/skeletons.cjs.map +1 -1
  49. package/dist/skeletons.js +1 -1
  50. package/package.json +1 -1
  51. package/dist/chunk-3FJUCHCC.js.map +0 -1
  52. package/dist/chunk-3IH7IJ5S.js.map +0 -1
  53. package/dist/chunk-BEPH6PPL.js.map +0 -1
  54. package/dist/chunk-DNRQVM3Q.js.map +0 -1
  55. package/dist/chunk-FSDQHBI2.js.map +0 -1
  56. package/dist/chunk-QQ25RKOO.js.map +0 -1
  57. package/dist/chunk-WL67XCH5.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { useFloating, useDismiss } from './chunk-3FJUCHCC.js';
1
+ import { useFloating, useDismiss } from './chunk-IPR5JAVF.js';
2
2
  import { useMachine } from './chunk-NNOF7KUH.js';
3
3
  import { findFirstElement, mergeSlotProps } from './chunk-F77KIPOH.js';
4
4
  import { IrisHeaderLayout, IrisSidebarLayout } from './chunk-3V46HGRC.js';
@@ -7,7 +7,7 @@ import { IrisSortable } from './chunk-W3B6E5GN.js';
7
7
  import { useStore } from './chunk-75GSYEJS.js';
8
8
  import { useI18n } from './chunk-TCAXRWT6.js';
9
9
  import { defineComponent, inject, h, computed, shallowRef, onBeforeUnmount, watch, ref, provide, useId, nextTick, Teleport, Fragment, watchEffect, toValue, onMounted } from 'vue';
10
- import { visibleNav, findNavPath, createExpansion, branchTrail, createFloatingMachine, findNavNode, isBranch, firstLeaf, matchTypeahead, nextEnabledIndex, isClosable, createAdminShell } from '@iris-ui-kit/core';
10
+ import { visibleNav, findNavPath, createExpansion, branchTrail, isBranch, createFloatingMachine, findNavNode, firstLeaf, matchTypeahead, nextEnabledIndex, isClosable, createAdminShell } from '@iris-ui-kit/core';
11
11
  export { createAdminPreferences, createTabsNav, filterNavByAccess, findNavNode, findNavPath, firstLeaf, flattenNav, isBranch, isClosable, localStorageAdminPreferencesStorage, nodeAllowsRoles, visibleNav } from '@iris-ui-kit/core';
12
12
  import { defaultIconRegistry, resolveThemedIcon } from '@iris-ui-kit/icons';
13
13
 
@@ -73,7 +73,7 @@ var __NAV_MENU_STYLE_ID = "iris-nav-menu-styles";
73
73
  var CSS = `
74
74
  :where(.iris-nav-menu) {
75
75
  --iris-nav-indent-step: 16px;
76
- --iris-nav-item-padding-inline: 10px;
76
+ --iris-nav-item-padding-inline: 12px;
77
77
  --iris-nav-item-padding-block: 8px;
78
78
  --iris-nav-item-border-radius: var(--iris-radius-md, 6px);
79
79
  --iris-nav-item-hover: var(--iris-surface-hover, var(--iris-surface));
@@ -81,7 +81,7 @@ var CSS = `
81
81
  display: flex;
82
82
  flex-direction: column;
83
83
  align-items: stretch;
84
- gap: 2px;
84
+ gap: var(--iris-space-xxs, 4px);
85
85
  width: 100%;
86
86
  }
87
87
 
@@ -106,16 +106,16 @@ var CSS = `
106
106
  border: none;
107
107
  border-radius: var(--iris-nav-item-border-radius);
108
108
  background: transparent;
109
- color: var(--iris-foreground, var(--iris-color-text, var(--iris-foreground)));
109
+ color: var(--iris-foreground);
110
110
  font: inherit;
111
- font-size: 14px;
111
+ font-size: var(--iris-font-size-md, 14px);
112
112
  line-height: 1.2;
113
113
  font-weight: 400;
114
114
  text-align: start;
115
115
  cursor: pointer;
116
116
  opacity: 1;
117
117
  margin: 0;
118
- gap: 10px;
118
+ gap: var(--iris-space-sm, 12px);
119
119
  padding-block: var(--iris-nav-item-padding-block);
120
120
  padding-inline: var(--iris-nav-item-padding-inline);
121
121
  text-overflow: ellipsis;
@@ -128,7 +128,7 @@ var CSS = `
128
128
  }
129
129
 
130
130
  :where(.iris-nav-menu-item[data-depth='0']) {
131
- --iris-nav-item-padding-inline-start: 10px;
131
+ --iris-nav-item-padding-inline-start: 12px;
132
132
  }
133
133
 
134
134
  :where(.iris-nav-menu-item[data-depth='1']) {
@@ -176,7 +176,7 @@ var CSS = `
176
176
  background: var(--iris-nav-item-hover);
177
177
  }
178
178
 
179
- :where(.iris-nav-menu-item[data-active='true']) {
179
+ .iris-nav-menu-item[data-active='true'] {
180
180
  background: var(--iris-primary);
181
181
  color: var(--iris-primary-foreground, #fff);
182
182
  font-weight: 600;
@@ -205,13 +205,13 @@ var CSS = `
205
205
  }
206
206
 
207
207
  :where(.iris-nav-menu-badge) {
208
- margin-inline-start: 6px;
209
- font-size: 11px;
208
+ margin-inline-start: var(--iris-space-xs, 8px);
209
+ font-size: var(--iris-font-size-xs, 12px);
210
210
  line-height: 1;
211
- padding: 2px 6px;
211
+ padding: var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px);
212
212
  border-radius: 999px;
213
- background: var(--iris-danger, #e5484d);
214
- color: #fff;
213
+ background: var(--iris-danger, #ef4444);
214
+ color: var(--iris-primary-foreground, #fff);
215
215
  flex: 0 0 auto;
216
216
  }
217
217
 
@@ -241,44 +241,68 @@ var CSS = `
241
241
  opacity: 0.75;
242
242
  }
243
243
 
244
- :where(.iris-nav-menu-group--depth-0 > .iris-nav-menu-children) {
245
- display: none;
244
+ /* Vertical branches stay in the DOM and animate height via the grid-rows
245
+ trick (0fr \u2192 1fr); the inner wrapper provides the overflow clipping so the
246
+ transition is a smooth accordion without a max-height cap. */
247
+ :where(.iris-nav-menu--vertical .iris-nav-menu-group > .iris-nav-menu-children) {
248
+ display: grid;
249
+ grid-template-rows: 0fr;
250
+ opacity: 0;
251
+ visibility: hidden;
252
+ transition:
253
+ grid-template-rows 200ms ease,
254
+ opacity 150ms ease,
255
+ visibility 0s linear 200ms;
246
256
  }
247
257
 
248
- :where(.iris-nav-menu-group--depth-1 > .iris-nav-menu-children),
249
- :where(.iris-nav-menu-group--depth-2 > .iris-nav-menu-children),
250
- :where(.iris-nav-menu-group--depth-3 > .iris-nav-menu-children),
251
- :where(.iris-nav-menu-group--depth-4 > .iris-nav-menu-children),
252
- :where(.iris-nav-menu-group--depth-5 > .iris-nav-menu-children),
253
- :where(.iris-nav-menu-group--depth-6 > .iris-nav-menu-children),
254
- :where(.iris-nav-menu-group--depth-7 > .iris-nav-menu-children),
255
- :where(.iris-nav-menu-group--depth-8 > .iris-nav-menu-children),
256
- :where(.iris-nav-menu-group--depth-9 > .iris-nav-menu-children),
257
- :where(.iris-nav-menu-group > .iris-nav-menu-children) {
258
- display: none;
258
+ :where(.iris-nav-menu--vertical .iris-nav-menu-group > .iris-nav-menu-children > .iris-nav-menu-children-inner) {
259
+ overflow: hidden;
260
+ min-height: 0;
259
261
  }
260
262
 
261
- :where(.iris-nav-menu-group[data-open='true'] > .iris-nav-menu-children) {
262
- display: block;
263
+ :where(.iris-nav-menu--vertical .iris-nav-menu-group[data-open='true'] > .iris-nav-menu-children) {
264
+ grid-template-rows: 1fr;
265
+ opacity: 1;
266
+ visibility: visible;
267
+ transition:
268
+ grid-template-rows 200ms ease,
269
+ opacity 150ms ease;
263
270
  }
264
271
 
265
- :where(.iris-nav-menu--horizontal .iris-nav-menu-group > .iris-nav-menu-children) {
272
+ /* Flyout popups (horizontal mode + the collapsed rail) share the popup chrome
273
+ and are hard-hidden when closed (no height animation needed). */
274
+ :where(.iris-nav-menu--horizontal .iris-nav-menu-group > .iris-nav-menu-children),
275
+ :where(.iris-nav-menu--collapsed .iris-nav-menu-group > .iris-nav-menu-children) {
276
+ display: none;
266
277
  position: absolute;
267
278
  min-width: 220px;
268
- padding: 6px;
279
+ padding: var(--iris-space-xs, 8px);
269
280
  border: 1px solid var(--iris-border);
270
281
  border-radius: var(--iris-radius-md, 6px);
271
282
  background: var(--iris-surface);
272
283
  box-shadow: var(--iris-shadow-md);
273
284
  }
274
285
 
286
+ :where(.iris-nav-menu--horizontal .iris-nav-menu-group[data-open='true'] > .iris-nav-menu-children),
287
+ :where(.iris-nav-menu--collapsed .iris-nav-menu-group[data-open='true'] > .iris-nav-menu-children) {
288
+ display: block;
289
+ }
290
+
275
291
  :where(.iris-nav-menu--horizontal .iris-nav-menu-group[data-depth='0'] > .iris-nav-menu-children) {
276
292
  inset-block-start: 100%;
277
293
  inset-inline-start: 0;
278
294
  z-index: 60;
279
295
  }
280
296
 
281
- :where(.iris-nav-menu--horizontal .iris-nav-menu-group[data-depth]:not([data-depth='0']) > .iris-nav-menu-children) {
297
+ /* A9: the collapsed rail popup opens to the right of the icon. */
298
+ :where(.iris-nav-menu--collapsed .iris-nav-menu-group[data-depth='0'] > .iris-nav-menu-children) {
299
+ inset-block-start: 0;
300
+ inset-inline-start: 100%;
301
+ z-index: 60;
302
+ }
303
+
304
+ :where(.iris-nav-menu--horizontal .iris-nav-menu-group[data-depth]:not([data-depth='0']) > .iris-nav-menu-children),
305
+ :where(.iris-nav-menu--collapsed .iris-nav-menu-group[data-depth]:not([data-depth='0']) > .iris-nav-menu-children) {
282
306
  inset-block-start: 0;
283
307
  inset-inline-start: 100%;
284
308
  z-index: 61;
@@ -294,10 +318,20 @@ var CSS = `
294
318
  padding-inline: var(--iris-nav-item-padding-inline);
295
319
  }
296
320
 
321
+ /* A9: the collapsed rail stays centered even though rows carry data-depth
322
+ (the vertical indent rules would otherwise win the specificity tie). */
323
+ :where(.iris-nav-menu--collapsed .iris-nav-menu-item[data-depth]) {
324
+ justify-content: center;
325
+ padding-inline: var(--iris-nav-item-padding-inline);
326
+ }
327
+
297
328
  @media (prefers-reduced-motion: reduce) {
298
329
  :where(.iris-nav-menu-arrow) {
299
330
  transition: none;
300
331
  }
332
+ :where(.iris-nav-menu--vertical .iris-nav-menu-group > .iris-nav-menu-children) {
333
+ transition: none;
334
+ }
301
335
  }
302
336
  `.trim();
303
337
  var installed = false;
@@ -316,6 +350,8 @@ function installNavMenuStyles() {
316
350
  }
317
351
 
318
352
  // src/admin/NavMenu.ts
353
+ var HOVER_OPEN_DELAY = 100;
354
+ var HOVER_CLOSE_DELAY = 150;
319
355
  var IrisNavMenu = defineComponent({
320
356
  name: "IrisNavMenu",
321
357
  inheritAttrs: false,
@@ -347,6 +383,7 @@ var IrisNavMenu = defineComponent({
347
383
  const activePath = computed(
348
384
  () => props.activeKey ? findNavPath(props.items, props.activeKey).map((n) => n.key) : []
349
385
  );
386
+ const flyoutMode = computed(() => props.orientation === "horizontal" || props.collapsed);
350
387
  const isControlled = computed(() => props.expandedKeys !== void 0);
351
388
  const model = createExpansion({
352
389
  mode: "multiple",
@@ -361,12 +398,9 @@ var IrisNavMenu = defineComponent({
361
398
  const expanded = computed(
362
399
  () => isControlled.value ? props.expandedKeys : internalExpanded.value
363
400
  );
364
- watch(
365
- () => props.activeKey,
366
- (key) => {
367
- if (!isControlled.value) model.merge(key ? branchTrail(props.items, key) : []);
368
- }
369
- );
401
+ watch([() => props.activeKey, () => props.items], ([key]) => {
402
+ if (!isControlled.value) model.merge(key ? branchTrail(props.items, key) : []);
403
+ });
370
404
  const toggle = (key) => {
371
405
  if (isControlled.value) {
372
406
  const cur = props.expandedKeys;
@@ -382,17 +416,113 @@ var IrisNavMenu = defineComponent({
382
416
  const hovered = ref(null);
383
417
  const hoveredBranches = ref([]);
384
418
  const focusedBranches = ref([]);
419
+ const clickedBranches = ref([]);
420
+ let suppressFocusOpen = false;
421
+ let openTimer = null;
422
+ const closeTimers = /* @__PURE__ */ new Map();
423
+ const clearOpenTimer = () => {
424
+ if (openTimer) {
425
+ clearTimeout(openTimer);
426
+ openTimer = null;
427
+ }
428
+ };
429
+ const cancelClose = (key) => {
430
+ const timer = closeTimers.get(key);
431
+ if (timer) {
432
+ clearTimeout(timer);
433
+ closeTimers.delete(key);
434
+ }
435
+ };
436
+ const cancelAllTimers = () => {
437
+ clearOpenTimer();
438
+ for (const timer of closeTimers.values()) clearTimeout(timer);
439
+ closeTimers.clear();
440
+ };
441
+ onBeforeUnmount(cancelAllTimers);
442
+ const flyoutTriggers = /* @__PURE__ */ new Map();
443
+ const flyoutPanels = /* @__PURE__ */ new Map();
444
+ const setFlyoutRef = (map, key, el) => {
445
+ if (el instanceof HTMLElement) map.set(key, el);
446
+ else map.delete(key);
447
+ };
448
+ const positionFlyout = (key) => {
449
+ const trigger = flyoutTriggers.get(key);
450
+ const panel = flyoutPanels.get(key);
451
+ if (!trigger || !panel || typeof document === "undefined") return;
452
+ const rect = trigger.getBoundingClientRect();
453
+ const dir = getComputedStyle(trigger).direction;
454
+ panel.style.position = "fixed";
455
+ panel.style.top = `${props.collapsed ? rect.top : rect.bottom}px`;
456
+ if (dir === "rtl") {
457
+ panel.style.left = "";
458
+ panel.style.right = `${window.innerWidth - (props.collapsed ? rect.left : rect.right)}px`;
459
+ } else {
460
+ panel.style.right = "";
461
+ panel.style.left = `${props.collapsed ? rect.right : rect.left}px`;
462
+ }
463
+ };
464
+ onBeforeUnmount(() => {
465
+ detachViewportListeners();
466
+ flyoutTriggers.clear();
467
+ flyoutPanels.clear();
468
+ });
469
+ const isolateAtDepth = (key) => {
470
+ const depth = findNavPath(props.items, key).length - 1;
471
+ const isSameDepthOther = (k) => k !== key && findNavPath(props.items, k).length - 1 === depth;
472
+ for (const state of [hoveredBranches, clickedBranches, focusedBranches]) {
473
+ if (state.value.some(isSameDepthOther)) {
474
+ state.value = state.value.filter((k) => !isSameDepthOther(k));
475
+ }
476
+ }
477
+ };
478
+ const scheduleOpen = (key) => {
479
+ clearOpenTimer();
480
+ openTimer = setTimeout(() => {
481
+ openTimer = null;
482
+ isolateAtDepth(key);
483
+ setBranchInteraction(hoveredBranches, key, true);
484
+ }, HOVER_OPEN_DELAY);
485
+ };
486
+ const scheduleClose = (key) => {
487
+ cancelClose(key);
488
+ closeTimers.set(
489
+ key,
490
+ setTimeout(() => {
491
+ closeTimers.delete(key);
492
+ setBranchInteraction(hoveredBranches, key, false);
493
+ }, HOVER_CLOSE_DELAY)
494
+ );
495
+ };
385
496
  const closeHorizontalMenus = () => {
386
- if (props.orientation !== "horizontal") return;
497
+ if (!flyoutMode.value) return;
498
+ cancelAllTimers();
387
499
  hovered.value = null;
388
500
  hoveredBranches.value = [];
389
501
  focusedBranches.value = [];
502
+ clickedBranches.value = [];
390
503
  };
391
504
  const select = (node) => {
392
505
  if (node.disabled) return;
393
506
  closeHorizontalMenus();
394
507
  emit("select", node.key, node);
395
508
  };
509
+ const toggleFlyout = (key) => {
510
+ cancelAllTimers();
511
+ const current = clickedBranches.value;
512
+ if (current.includes(key)) {
513
+ clickedBranches.value = current.filter((k) => k !== key);
514
+ setBranchInteraction(hoveredBranches, key, false);
515
+ setBranchInteraction(focusedBranches, key, false);
516
+ } else {
517
+ isolateAtDepth(key);
518
+ clickedBranches.value = [...clickedBranches.value, key];
519
+ }
520
+ };
521
+ const openFlyout = (key) => {
522
+ cancelAllTimers();
523
+ isolateAtDepth(key);
524
+ setBranchInteraction(focusedBranches, key, true);
525
+ };
396
526
  const setBranchInteraction = (state, key, enabled) => {
397
527
  const current = state.value;
398
528
  if (enabled) {
@@ -410,8 +540,43 @@ var IrisNavMenu = defineComponent({
410
540
  const horizontalBranchVisible = (key, depth) => {
411
541
  const hoveredAtDepth = interactionKeyAtDepth(hoveredBranches.value, depth);
412
542
  if (hoveredAtDepth) return hoveredAtDepth === key;
543
+ const clickedAtDepth = interactionKeyAtDepth(clickedBranches.value, depth);
544
+ if (clickedAtDepth) return clickedAtDepth === key;
413
545
  return interactionKeyAtDepth(focusedBranches.value, depth) === key;
414
546
  };
547
+ const shownFlyoutKeys = computed(() => {
548
+ if (!flyoutMode.value) return [];
549
+ const keys = [];
550
+ for (const node of tree.value) {
551
+ if (isBranch(node) && horizontalBranchVisible(node.key, 0)) keys.push(node.key);
552
+ }
553
+ return keys;
554
+ });
555
+ const repositionOpenFlyouts = () => {
556
+ for (const key of shownFlyoutKeys.value) positionFlyout(key);
557
+ };
558
+ let viewportListenersActive = false;
559
+ const attachViewportListeners = () => {
560
+ if (viewportListenersActive || typeof document === "undefined") return;
561
+ viewportListenersActive = true;
562
+ document.addEventListener("scroll", repositionOpenFlyouts, true);
563
+ window.addEventListener("resize", repositionOpenFlyouts);
564
+ };
565
+ const detachViewportListeners = () => {
566
+ if (!viewportListenersActive || typeof document === "undefined") return;
567
+ viewportListenersActive = false;
568
+ document.removeEventListener("scroll", repositionOpenFlyouts, true);
569
+ window.removeEventListener("resize", repositionOpenFlyouts);
570
+ };
571
+ watch(
572
+ shownFlyoutKeys,
573
+ (keys, prev) => {
574
+ if (keys.length > 0 && prev.length === 0) attachViewportListeners();
575
+ else if (keys.length === 0 && prev.length > 0) detachViewportListeners();
576
+ for (const key of keys) positionFlyout(key);
577
+ },
578
+ { flush: "post" }
579
+ );
415
580
  const itemClass = (opts) => {
416
581
  const depth = Math.min(9, opts.depth);
417
582
  return [
@@ -434,81 +599,47 @@ var IrisNavMenu = defineComponent({
434
599
  },
435
600
  String(node.badge)
436
601
  ) : null;
437
- const renderCollapsed = () => tree.value.map((node) => {
602
+ const renderItem = (node, depth) => {
438
603
  const branch = isBranch(node);
439
- const active = branch ? activePath.value.includes(node.key) : node.key === props.activeKey;
440
- return h(
604
+ const horizontal = flyoutMode.value;
605
+ const open = expanded.value.includes(node.key);
606
+ const shown = horizontal ? horizontalBranchVisible(node.key, depth) : open;
607
+ const active = node.key === props.activeKey;
608
+ const trail = branch && !active && activePath.value.includes(node.key);
609
+ const rowHovered = hovered.value === node.key || horizontal && shown;
610
+ const arrowReversed = branch && !node.disabled && (active || trail || shown || rowHovered);
611
+ const isFlyoutRoot = horizontal && branch && depth === 0;
612
+ const row = h(
441
613
  "button",
442
614
  {
443
- key: node.key,
444
615
  type: "button",
616
+ ref: (el) => {
617
+ if (isFlyoutRoot) setFlyoutRef(flyoutTriggers, node.key, el);
618
+ },
445
619
  class: [
446
620
  ...itemClass({
447
- depth: 0,
621
+ depth,
448
622
  active,
449
- trail: false,
450
- hovered: hovered.value === node.key,
451
- open: false,
623
+ trail,
624
+ hovered: rowHovered,
625
+ open: branch ? shown : false,
452
626
  disabled: !!node.disabled,
453
627
  branch
454
628
  }),
455
- "is-collapsed"
629
+ ...props.collapsed && depth === 0 ? ["is-collapsed"] : []
456
630
  ],
457
631
  "data-iris-nav-item": "",
458
632
  "data-key": node.key,
459
- "data-active": active ? "true" : void 0,
460
- "data-branch": branch ? "true" : void 0,
461
- "data-hovered": hovered.value === node.key ? "true" : void 0,
462
- disabled: node.disabled,
463
- title: node.title,
464
- "aria-label": branch ? `${node.title} (section)` : node.title,
465
- "aria-current": active && !branch ? "page" : void 0,
466
- onMouseenter: () => {
467
- if (!node.disabled) hovered.value = node.key;
468
- },
469
- onMouseleave: (event) => {
470
- if (hovered.value === node.key && !keepsPointerInside(event.currentTarget, event)) {
471
- hovered.value = null;
472
- }
473
- },
474
- onClick: () => select(branch ? firstLeaf(node) : node)
475
- },
476
- [
477
- iconNode(node, 20) ?? h("span", { class: "iris-nav-menu-fallback-icon" }, node.title.charAt(0))
478
- ]
479
- );
480
- });
481
- const renderItem = (node, depth) => {
482
- const branch = isBranch(node);
483
- const open = expanded.value.includes(node.key);
484
- const horizontal = props.orientation === "horizontal";
485
- const shown = horizontal ? horizontalBranchVisible(node.key, depth) : open;
486
- const active = node.key === props.activeKey;
487
- const trail = branch && !active && activePath.value.includes(node.key);
488
- const arrowReversed = branch && !node.disabled && (active || trail || shown || hovered.value === node.key);
489
- const row = h(
490
- "button",
491
- {
492
- type: "button",
493
- class: itemClass({
494
- depth,
495
- active,
496
- trail,
497
- hovered: hovered.value === node.key,
498
- open: branch ? shown : false,
499
- disabled: !!node.disabled,
500
- branch
501
- }),
502
- "data-iris-nav-item": "",
503
- "data-key": node.key,
504
633
  "data-branch": branch ? "true" : void 0,
505
634
  "data-active": active ? "true" : void 0,
506
635
  "data-active-trail": trail ? "true" : void 0,
507
636
  "data-open": branch && shown ? "true" : void 0,
508
- "data-hovered": hovered.value === node.key ? "true" : void 0,
637
+ "data-hovered": rowHovered ? "true" : void 0,
509
638
  "data-depth": String(depth),
510
639
  "data-orientation": props.orientation,
511
640
  disabled: node.disabled,
641
+ title: props.collapsed ? node.title : void 0,
642
+ "aria-label": props.collapsed ? branch ? `${node.title} (section)` : node.title : void 0,
512
643
  "aria-expanded": branch ? shown ? "true" : "false" : void 0,
513
644
  "aria-current": active ? "page" : void 0,
514
645
  onMouseenter: () => {
@@ -519,7 +650,17 @@ var IrisNavMenu = defineComponent({
519
650
  hovered.value = null;
520
651
  }
521
652
  },
522
- onClick: () => branch ? toggle(node.key) : select(node)
653
+ onClick: () => {
654
+ if (props.collapsed && branch) {
655
+ select(firstLeaf(node));
656
+ } else if (horizontal && branch) {
657
+ toggleFlyout(node.key);
658
+ } else if (branch) {
659
+ toggle(node.key);
660
+ } else {
661
+ select(node);
662
+ }
663
+ }
523
664
  },
524
665
  [
525
666
  iconNode(node),
@@ -531,54 +672,49 @@ var IrisNavMenu = defineComponent({
531
672
  node.title
532
673
  ),
533
674
  badgeNode(node),
534
- branch ? h(IrisIcon, {
535
- name: horizontal && depth === 0 ? "chevron-down" : "chevron-right",
675
+ branch && !(props.collapsed && depth === 0) ? h(IrisIcon, {
676
+ name: horizontal && depth === 0 && !props.collapsed ? "chevron-down" : "chevron-right",
536
677
  size: 16,
537
678
  class: "iris-nav-menu-arrow",
538
679
  "data-reversed": arrowReversed ? "true" : void 0
539
680
  }) : null
540
681
  ]
541
682
  );
542
- if (!branch || !horizontal && !open)
543
- return h(
544
- "div",
545
- {
546
- key: node.key,
547
- "data-iris-nav-group": branch ? "" : void 0,
548
- class: ["iris-nav-menu-group", `iris-nav-menu-group--depth-${Math.min(9, depth)}`]
549
- },
550
- [row]
551
- );
552
683
  return h(
553
684
  "div",
554
685
  {
555
686
  key: node.key,
556
- "data-iris-nav-group": "",
557
- "data-open": shown ? "true" : void 0,
687
+ "data-iris-nav-group": branch ? "" : void 0,
688
+ "data-open": branch && shown ? "true" : void 0,
558
689
  "data-depth": String(depth),
559
690
  class: ["iris-nav-menu-group", `iris-nav-menu-group--depth-${Math.min(9, depth)}`],
560
- ...horizontal ? {
691
+ ...horizontal && branch ? {
561
692
  onMouseenter: (event) => {
562
- if (!keepsPointerInside(event.currentTarget, event)) {
563
- setBranchInteraction(hoveredBranches, node.key, true);
693
+ if (!node.disabled && !keepsPointerInside(event.currentTarget, event)) {
694
+ cancelClose(node.key);
695
+ scheduleOpen(node.key);
564
696
  }
565
697
  },
566
698
  onMouseover: (event) => {
567
- if (!keepsPointerInside(event.currentTarget, event)) {
568
- setBranchInteraction(hoveredBranches, node.key, true);
699
+ if (!node.disabled && !keepsPointerInside(event.currentTarget, event)) {
700
+ cancelClose(node.key);
701
+ scheduleOpen(node.key);
569
702
  }
570
703
  },
571
704
  onMouseout: (event) => {
572
- if (!keepsPointerInside(event.currentTarget, event)) {
573
- setBranchInteraction(hoveredBranches, node.key, false);
705
+ if (!node.disabled && !keepsPointerInside(event.currentTarget, event)) {
706
+ scheduleClose(node.key);
574
707
  }
575
708
  },
576
709
  onMouseleave: (event) => {
577
- if (!keepsPointerInside(event.currentTarget, event)) {
578
- setBranchInteraction(hoveredBranches, node.key, false);
710
+ if (!node.disabled && !keepsPointerInside(event.currentTarget, event)) {
711
+ scheduleClose(node.key);
579
712
  }
580
713
  },
581
- onFocusin: () => setBranchInteraction(focusedBranches, node.key, true),
714
+ onFocusin: () => {
715
+ if (suppressFocusOpen) return;
716
+ setBranchInteraction(focusedBranches, node.key, true);
717
+ },
582
718
  onFocusout: (event) => {
583
719
  const group = event.currentTarget;
584
720
  const next = event.relatedTarget;
@@ -590,19 +726,45 @@ var IrisNavMenu = defineComponent({
590
726
  },
591
727
  [
592
728
  row,
593
- h(
729
+ branch ? h(
594
730
  "div",
595
731
  {
596
732
  class: "iris-nav-menu-children",
597
733
  "data-iris-nav-children": "",
734
+ ref: (el) => {
735
+ if (isFlyoutRoot) setFlyoutRef(flyoutPanels, node.key, el);
736
+ },
598
737
  role: "group",
599
- "aria-hidden": horizontal && !shown ? "true" : void 0
738
+ "aria-hidden": shown ? void 0 : "true"
600
739
  },
601
- (node.children ?? []).map((child) => renderItem(child, depth + 1))
602
- )
740
+ [
741
+ h(
742
+ "div",
743
+ { class: "iris-nav-menu-children-inner" },
744
+ (node.children ?? []).map((child) => renderItem(child, depth + 1))
745
+ )
746
+ ]
747
+ ) : null
603
748
  ]
604
749
  );
605
750
  };
751
+ const rootRef = shallowRef(null);
752
+ watch(
753
+ [() => props.activeKey, () => props.items],
754
+ () => {
755
+ if (!props.activeKey) return;
756
+ const root = rootRef.value;
757
+ if (!root) return;
758
+ const el = root.querySelector('[data-iris-nav-item][data-active="true"]');
759
+ if (!el || typeof el.scrollIntoView !== "function") return;
760
+ let behavior = "auto";
761
+ if (typeof window !== "undefined" && typeof window.matchMedia === "function" && !window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
762
+ behavior = "smooth";
763
+ }
764
+ el.scrollIntoView({ block: "nearest", inline: "nearest", behavior });
765
+ },
766
+ { flush: "post" }
767
+ );
606
768
  const onKeydown = (e) => {
607
769
  const root = e.currentTarget;
608
770
  const buttons = Array.from(root.querySelectorAll("[data-iris-nav-item]")).filter(
@@ -611,15 +773,33 @@ var IrisNavMenu = defineComponent({
611
773
  if (buttons.length === 0) return;
612
774
  const idx = buttons.indexOf(document.activeElement);
613
775
  const focusAt = (i) => buttons[(i + buttons.length) % buttons.length]?.focus();
776
+ const key = idx >= 0 ? buttons[idx]?.getAttribute("data-key") : void 0;
777
+ const node = key ? findNavNode(props.items, key) : void 0;
778
+ const depth = idx >= 0 ? Number(buttons[idx]?.getAttribute("data-depth") ?? 0) : 0;
614
779
  switch (e.key) {
615
- case "ArrowDown":
780
+ case "ArrowDown": {
616
781
  e.preventDefault();
782
+ if (flyoutMode.value && node && isBranch(node)) {
783
+ openFlyout(key);
784
+ const group = buttons[idx].closest("[data-iris-nav-group]");
785
+ void nextTick(() => {
786
+ group?.querySelector("[data-iris-nav-children] [data-iris-nav-item]")?.focus();
787
+ });
788
+ return;
789
+ }
617
790
  focusAt(idx < 0 ? 0 : idx + 1);
618
791
  return;
619
- case "ArrowUp":
792
+ }
793
+ case "ArrowUp": {
620
794
  e.preventDefault();
795
+ if (flyoutMode.value && node && isBranch(node) && horizontalBranchVisible(key, depth)) {
796
+ setBranchInteraction(focusedBranches, key, false);
797
+ setBranchInteraction(clickedBranches, key, false);
798
+ return;
799
+ }
621
800
  focusAt(idx < 0 ? buttons.length - 1 : idx - 1);
622
801
  return;
802
+ }
623
803
  case "Home":
624
804
  e.preventDefault();
625
805
  buttons[0]?.focus();
@@ -628,23 +808,56 @@ var IrisNavMenu = defineComponent({
628
808
  e.preventDefault();
629
809
  buttons[buttons.length - 1]?.focus();
630
810
  return;
811
+ case "Escape": {
812
+ if (flyoutMode.value) {
813
+ const openKeys = [
814
+ ...hoveredBranches.value,
815
+ ...clickedBranches.value,
816
+ ...focusedBranches.value
817
+ ];
818
+ if (openKeys.length > 0) {
819
+ e.preventDefault();
820
+ const openTop = openKeys.find((k) => findNavPath(props.items, k).length === 1);
821
+ closeHorizontalMenus();
822
+ if (openTop) {
823
+ suppressFocusOpen = true;
824
+ buttons.find((b) => b.getAttribute("data-key") === openTop)?.focus();
825
+ void nextTick(() => {
826
+ suppressFocusOpen = false;
827
+ });
828
+ }
829
+ }
830
+ }
831
+ return;
832
+ }
631
833
  }
632
- if (props.collapsed || idx < 0) return;
633
- const key = buttons[idx]?.getAttribute("data-key");
634
- if (!key) return;
635
- const node = findNavNode(props.items, key);
636
- if (e.key === "ArrowRight" && node && isBranch(node)) {
834
+ if (props.collapsed || idx < 0 || !key || !node) return;
835
+ if (e.key === "ArrowRight") {
637
836
  e.preventDefault();
638
- if (!expanded.value.includes(key)) toggle(key);
639
- else focusAt(idx + 1);
837
+ if (isBranch(node)) {
838
+ if (flyoutMode.value || !expanded.value.includes(key)) {
839
+ if (flyoutMode.value) openFlyout(key);
840
+ else toggle(key);
841
+ const group = buttons[idx].closest("[data-iris-nav-group]");
842
+ void nextTick(() => {
843
+ group?.querySelector("[data-iris-nav-children] [data-iris-nav-item]")?.focus();
844
+ });
845
+ } else {
846
+ focusAt(idx + 1);
847
+ }
848
+ }
640
849
  } else if (e.key === "ArrowLeft") {
641
- if (node && isBranch(node) && expanded.value.includes(key)) {
642
- e.preventDefault();
850
+ e.preventDefault();
851
+ if (flyoutMode.value && isBranch(node)) {
852
+ setBranchInteraction(focusedBranches, key, false);
853
+ setBranchInteraction(clickedBranches, key, false);
854
+ return;
855
+ }
856
+ if (isBranch(node) && expanded.value.includes(key)) {
643
857
  toggle(key);
644
858
  } else {
645
859
  const parentKey = findNavPath(props.items, key).at(-2)?.key;
646
860
  if (parentKey) {
647
- e.preventDefault();
648
861
  buttons.find((b) => b.getAttribute("data-key") === parentKey)?.focus();
649
862
  }
650
863
  }
@@ -659,6 +872,9 @@ var IrisNavMenu = defineComponent({
659
872
  "nav",
660
873
  {
661
874
  ...attrs,
875
+ ref: (el) => {
876
+ rootRef.value = el ?? null;
877
+ },
662
878
  class: [...menuClass, attrs.class],
663
879
  "data-iris-nav-menu": "",
664
880
  "data-collapsed": props.collapsed ? "true" : void 0,
@@ -667,7 +883,7 @@ var IrisNavMenu = defineComponent({
667
883
  onKeydown,
668
884
  style: attrs.style
669
885
  },
670
- props.collapsed ? renderCollapsed() : tree.value.map((node) => renderItem(node, 0))
886
+ tree.value.map((node) => renderItem(node, 0))
671
887
  );
672
888
  }
673
889
  });
@@ -737,11 +953,11 @@ var IrisBreadcrumb = defineComponent({
737
953
  style: {
738
954
  display: "inline-flex",
739
955
  alignItems: "center",
740
- gap: "6px",
956
+ gap: "var(--iris-space-xs, 8px)",
741
957
  margin: "0",
742
958
  padding: "0",
743
959
  listStyle: "none",
744
- fontSize: "14px"
960
+ fontSize: "var(--iris-font-size-md, 14px)"
745
961
  }
746
962
  },
747
963
  items
@@ -1069,12 +1285,12 @@ var IrisDropdownMenu = defineComponent({
1069
1285
  onKeydown: onKeyDown,
1070
1286
  style: {
1071
1287
  ...floatingStyles.value,
1072
- background: "var(--iris-surface)",
1288
+ background: "var(--iris-surface-floating)",
1073
1289
  color: "var(--iris-foreground)",
1074
1290
  border: "1px solid var(--iris-border)",
1075
1291
  borderRadius: "var(--iris-radius-md)",
1076
1292
  padding: "var(--iris-padding-sm)",
1077
- boxShadow: "0 8px 24px -8px rgba(0, 0, 0, 0.16), 0 4px 8px -2px rgba(0, 0, 0, 0.08)",
1293
+ boxShadow: "var(--iris-shadow-lg)",
1078
1294
  minWidth: "160px",
1079
1295
  outline: "none",
1080
1296
  zIndex: "1000",
@@ -1139,14 +1355,14 @@ var IrisDropdownItem = defineComponent({
1139
1355
  display: "flex",
1140
1356
  alignItems: "center",
1141
1357
  gap: "var(--iris-gap-sm)",
1142
- padding: "6px var(--iris-padding-md)",
1358
+ padding: "var(--iris-padding-sm, 6px) var(--iris-padding-md)",
1143
1359
  borderRadius: "var(--iris-radius-sm)",
1144
1360
  cursor: props.disabled ? "not-allowed" : "pointer",
1145
1361
  opacity: props.disabled ? "0.5" : "1",
1146
1362
  background: hovered.value && !props.disabled ? "var(--iris-surface-hover)" : "transparent",
1147
1363
  color: "inherit",
1148
1364
  outline: "none",
1149
- fontSize: "14px",
1365
+ fontSize: "var(--iris-font-size-md, 14px)",
1150
1366
  transition: "background-color 80ms ease",
1151
1367
  ...attrs.style ?? {}
1152
1368
  }
@@ -1259,12 +1475,12 @@ var IrisAdminTabs = defineComponent({
1259
1475
  style: {
1260
1476
  display: "inline-flex",
1261
1477
  alignItems: "center",
1262
- gap: "6px",
1478
+ gap: "var(--iris-space-xs, 8px)",
1263
1479
  border: "none",
1264
1480
  background: "transparent",
1265
1481
  color: "inherit",
1266
1482
  font: "inherit",
1267
- fontSize: "13px",
1483
+ fontSize: "var(--iris-font-size-sm, 13px)",
1268
1484
  cursor: "pointer",
1269
1485
  padding: "0"
1270
1486
  },
@@ -1314,7 +1530,7 @@ var IrisAdminTabs = defineComponent({
1314
1530
  display: "inline-flex",
1315
1531
  alignItems: "center",
1316
1532
  gap: "4px",
1317
- padding: "5px 8px 5px 10px",
1533
+ padding: "var(--iris-padding-sm, 6px) var(--iris-space-xs, 8px) var(--iris-padding-sm, 6px) var(--iris-space-sm, 12px)",
1318
1534
  borderRadius: "var(--iris-radius-md, 6px)",
1319
1535
  border: `1px solid ${active ? "var(--iris-primary)" : "var(--iris-border)"}`,
1320
1536
  background: active ? "var(--iris-primary)" : "var(--iris-surface)",
@@ -1330,27 +1546,23 @@ var IrisAdminTabs = defineComponent({
1330
1546
  default: () => [
1331
1547
  h(
1332
1548
  IrisDropdownTrigger,
1333
- { "aria-label": tr("admin.tabActions") },
1334
1549
  {
1335
- default: () => h(
1336
- "span",
1337
- {
1338
- "data-iris-tab-actions": "",
1339
- style: {
1340
- display: "inline-flex",
1341
- alignItems: "center",
1342
- justifyContent: "center",
1343
- width: "28px",
1344
- height: "28px",
1345
- borderRadius: "var(--iris-radius-md, 6px)",
1346
- border: "1px solid var(--iris-border)",
1347
- background: "var(--iris-surface)",
1348
- color: "var(--iris-foreground)",
1349
- cursor: "pointer"
1350
- }
1351
- },
1352
- [h(IrisIcon, { name: "more-horizontal", size: 16 })]
1353
- )
1550
+ "aria-label": tr("admin.tabActions"),
1551
+ style: {
1552
+ display: "inline-flex",
1553
+ alignItems: "center",
1554
+ justifyContent: "center",
1555
+ width: "28px",
1556
+ height: "28px",
1557
+ borderRadius: "var(--iris-radius-md, 6px)",
1558
+ border: "1px solid var(--iris-border)",
1559
+ background: "var(--iris-surface)",
1560
+ color: "var(--iris-foreground)",
1561
+ cursor: "pointer"
1562
+ }
1563
+ },
1564
+ {
1565
+ default: () => h(IrisIcon, { name: "more-horizontal", size: 16 })
1354
1566
  }
1355
1567
  ),
1356
1568
  h(IrisDropdownMenu, null, {
@@ -1379,8 +1591,8 @@ var IrisAdminTabs = defineComponent({
1379
1591
  style: {
1380
1592
  display: "flex",
1381
1593
  alignItems: "center",
1382
- gap: "6px",
1383
- padding: "6px 10px",
1594
+ gap: "var(--iris-space-xs, 8px)",
1595
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
1384
1596
  borderBottom: "1px solid var(--iris-border)",
1385
1597
  background: "var(--iris-background)",
1386
1598
  ...attrs.style ?? {}
@@ -1410,7 +1622,7 @@ var IrisAdminTabs = defineComponent({
1410
1622
  orientation: "horizontal",
1411
1623
  style: {
1412
1624
  alignItems: "center",
1413
- gap: "6px",
1625
+ gap: "var(--iris-space-xs, 8px)",
1414
1626
  width: "max-content"
1415
1627
  }
1416
1628
  },
@@ -1537,12 +1749,12 @@ var IrisAdminLayout = defineComponent({
1537
1749
  style: {
1538
1750
  display: "flex",
1539
1751
  alignItems: "center",
1540
- gap: "10px",
1752
+ gap: "var(--iris-space-sm, 12px)",
1541
1753
  height: "52px",
1542
- padding: "0 16px",
1754
+ padding: "0 var(--iris-space-md, 16px)",
1543
1755
  color: "var(--iris-foreground)",
1544
1756
  fontWeight: "700",
1545
- fontSize: "16px",
1757
+ fontSize: "var(--iris-font-size-lg, 16px)",
1546
1758
  whiteSpace: "nowrap",
1547
1759
  overflow: "hidden",
1548
1760
  flexShrink: 0
@@ -1821,5 +2033,5 @@ function useAdminPreferences(preferences, hydrate = true) {
1821
2033
  }
1822
2034
 
1823
2035
  export { DropdownContextKey, IrisAdminBreadcrumb, IrisAdminLayout, IrisAdminTabs, IrisBreadcrumb, IrisBreadcrumbItem, IrisDropdown, IrisDropdownItem, IrisDropdownMenu, IrisDropdownSeparator, IrisDropdownTrigger, IrisIcon, IrisNavMenu, useAdminPreferences, useAdminShell, useTabsNav };
1824
- //# sourceMappingURL=chunk-QQ25RKOO.js.map
1825
- //# sourceMappingURL=chunk-QQ25RKOO.js.map
2036
+ //# sourceMappingURL=chunk-ZWFSGMOB.js.map
2037
+ //# sourceMappingURL=chunk-ZWFSGMOB.js.map