@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
package/dist/index.cjs CHANGED
@@ -605,6 +605,7 @@ function useGroupedView(config) {
605
605
  });
606
606
  return {
607
607
  store: controller.store,
608
+ expansion: controller.expansion,
608
609
  setRows: (rows, columns) => controller.setRows(rows, columns),
609
610
  toggleGroup: (key) => controller.toggleGroup(key),
610
611
  expandGroup: (key) => controller.expandGroup(key),
@@ -912,7 +913,7 @@ var __NAV_MENU_STYLE_ID = "iris-nav-menu-styles";
912
913
  var CSS2 = `
913
914
  :where(.iris-nav-menu) {
914
915
  --iris-nav-indent-step: 16px;
915
- --iris-nav-item-padding-inline: 10px;
916
+ --iris-nav-item-padding-inline: 12px;
916
917
  --iris-nav-item-padding-block: 8px;
917
918
  --iris-nav-item-border-radius: var(--iris-radius-md, 6px);
918
919
  --iris-nav-item-hover: var(--iris-surface-hover, var(--iris-surface));
@@ -920,7 +921,7 @@ var CSS2 = `
920
921
  display: flex;
921
922
  flex-direction: column;
922
923
  align-items: stretch;
923
- gap: 2px;
924
+ gap: var(--iris-space-xxs, 4px);
924
925
  width: 100%;
925
926
  }
926
927
 
@@ -945,16 +946,16 @@ var CSS2 = `
945
946
  border: none;
946
947
  border-radius: var(--iris-nav-item-border-radius);
947
948
  background: transparent;
948
- color: var(--iris-foreground, var(--iris-color-text, var(--iris-foreground)));
949
+ color: var(--iris-foreground);
949
950
  font: inherit;
950
- font-size: 14px;
951
+ font-size: var(--iris-font-size-md, 14px);
951
952
  line-height: 1.2;
952
953
  font-weight: 400;
953
954
  text-align: start;
954
955
  cursor: pointer;
955
956
  opacity: 1;
956
957
  margin: 0;
957
- gap: 10px;
958
+ gap: var(--iris-space-sm, 12px);
958
959
  padding-block: var(--iris-nav-item-padding-block);
959
960
  padding-inline: var(--iris-nav-item-padding-inline);
960
961
  text-overflow: ellipsis;
@@ -967,7 +968,7 @@ var CSS2 = `
967
968
  }
968
969
 
969
970
  :where(.iris-nav-menu-item[data-depth='0']) {
970
- --iris-nav-item-padding-inline-start: 10px;
971
+ --iris-nav-item-padding-inline-start: 12px;
971
972
  }
972
973
 
973
974
  :where(.iris-nav-menu-item[data-depth='1']) {
@@ -1015,7 +1016,7 @@ var CSS2 = `
1015
1016
  background: var(--iris-nav-item-hover);
1016
1017
  }
1017
1018
 
1018
- :where(.iris-nav-menu-item[data-active='true']) {
1019
+ .iris-nav-menu-item[data-active='true'] {
1019
1020
  background: var(--iris-primary);
1020
1021
  color: var(--iris-primary-foreground, #fff);
1021
1022
  font-weight: 600;
@@ -1044,13 +1045,13 @@ var CSS2 = `
1044
1045
  }
1045
1046
 
1046
1047
  :where(.iris-nav-menu-badge) {
1047
- margin-inline-start: 6px;
1048
- font-size: 11px;
1048
+ margin-inline-start: var(--iris-space-xs, 8px);
1049
+ font-size: var(--iris-font-size-xs, 12px);
1049
1050
  line-height: 1;
1050
- padding: 2px 6px;
1051
+ padding: var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px);
1051
1052
  border-radius: 999px;
1052
- background: var(--iris-danger, #e5484d);
1053
- color: #fff;
1053
+ background: var(--iris-danger, #ef4444);
1054
+ color: var(--iris-primary-foreground, #fff);
1054
1055
  flex: 0 0 auto;
1055
1056
  }
1056
1057
 
@@ -1080,44 +1081,68 @@ var CSS2 = `
1080
1081
  opacity: 0.75;
1081
1082
  }
1082
1083
 
1083
- :where(.iris-nav-menu-group--depth-0 > .iris-nav-menu-children) {
1084
- display: none;
1084
+ /* Vertical branches stay in the DOM and animate height via the grid-rows
1085
+ trick (0fr \u2192 1fr); the inner wrapper provides the overflow clipping so the
1086
+ transition is a smooth accordion without a max-height cap. */
1087
+ :where(.iris-nav-menu--vertical .iris-nav-menu-group > .iris-nav-menu-children) {
1088
+ display: grid;
1089
+ grid-template-rows: 0fr;
1090
+ opacity: 0;
1091
+ visibility: hidden;
1092
+ transition:
1093
+ grid-template-rows 200ms ease,
1094
+ opacity 150ms ease,
1095
+ visibility 0s linear 200ms;
1085
1096
  }
1086
1097
 
1087
- :where(.iris-nav-menu-group--depth-1 > .iris-nav-menu-children),
1088
- :where(.iris-nav-menu-group--depth-2 > .iris-nav-menu-children),
1089
- :where(.iris-nav-menu-group--depth-3 > .iris-nav-menu-children),
1090
- :where(.iris-nav-menu-group--depth-4 > .iris-nav-menu-children),
1091
- :where(.iris-nav-menu-group--depth-5 > .iris-nav-menu-children),
1092
- :where(.iris-nav-menu-group--depth-6 > .iris-nav-menu-children),
1093
- :where(.iris-nav-menu-group--depth-7 > .iris-nav-menu-children),
1094
- :where(.iris-nav-menu-group--depth-8 > .iris-nav-menu-children),
1095
- :where(.iris-nav-menu-group--depth-9 > .iris-nav-menu-children),
1096
- :where(.iris-nav-menu-group > .iris-nav-menu-children) {
1097
- display: none;
1098
+ :where(.iris-nav-menu--vertical .iris-nav-menu-group > .iris-nav-menu-children > .iris-nav-menu-children-inner) {
1099
+ overflow: hidden;
1100
+ min-height: 0;
1098
1101
  }
1099
1102
 
1100
- :where(.iris-nav-menu-group[data-open='true'] > .iris-nav-menu-children) {
1101
- display: block;
1103
+ :where(.iris-nav-menu--vertical .iris-nav-menu-group[data-open='true'] > .iris-nav-menu-children) {
1104
+ grid-template-rows: 1fr;
1105
+ opacity: 1;
1106
+ visibility: visible;
1107
+ transition:
1108
+ grid-template-rows 200ms ease,
1109
+ opacity 150ms ease;
1102
1110
  }
1103
1111
 
1104
- :where(.iris-nav-menu--horizontal .iris-nav-menu-group > .iris-nav-menu-children) {
1112
+ /* Flyout popups (horizontal mode + the collapsed rail) share the popup chrome
1113
+ and are hard-hidden when closed (no height animation needed). */
1114
+ :where(.iris-nav-menu--horizontal .iris-nav-menu-group > .iris-nav-menu-children),
1115
+ :where(.iris-nav-menu--collapsed .iris-nav-menu-group > .iris-nav-menu-children) {
1116
+ display: none;
1105
1117
  position: absolute;
1106
1118
  min-width: 220px;
1107
- padding: 6px;
1119
+ padding: var(--iris-space-xs, 8px);
1108
1120
  border: 1px solid var(--iris-border);
1109
1121
  border-radius: var(--iris-radius-md, 6px);
1110
1122
  background: var(--iris-surface);
1111
1123
  box-shadow: var(--iris-shadow-md);
1112
1124
  }
1113
1125
 
1126
+ :where(.iris-nav-menu--horizontal .iris-nav-menu-group[data-open='true'] > .iris-nav-menu-children),
1127
+ :where(.iris-nav-menu--collapsed .iris-nav-menu-group[data-open='true'] > .iris-nav-menu-children) {
1128
+ display: block;
1129
+ }
1130
+
1114
1131
  :where(.iris-nav-menu--horizontal .iris-nav-menu-group[data-depth='0'] > .iris-nav-menu-children) {
1115
1132
  inset-block-start: 100%;
1116
1133
  inset-inline-start: 0;
1117
1134
  z-index: 60;
1118
1135
  }
1119
1136
 
1120
- :where(.iris-nav-menu--horizontal .iris-nav-menu-group[data-depth]:not([data-depth='0']) > .iris-nav-menu-children) {
1137
+ /* A9: the collapsed rail popup opens to the right of the icon. */
1138
+ :where(.iris-nav-menu--collapsed .iris-nav-menu-group[data-depth='0'] > .iris-nav-menu-children) {
1139
+ inset-block-start: 0;
1140
+ inset-inline-start: 100%;
1141
+ z-index: 60;
1142
+ }
1143
+
1144
+ :where(.iris-nav-menu--horizontal .iris-nav-menu-group[data-depth]:not([data-depth='0']) > .iris-nav-menu-children),
1145
+ :where(.iris-nav-menu--collapsed .iris-nav-menu-group[data-depth]:not([data-depth='0']) > .iris-nav-menu-children) {
1121
1146
  inset-block-start: 0;
1122
1147
  inset-inline-start: 100%;
1123
1148
  z-index: 61;
@@ -1133,10 +1158,20 @@ var CSS2 = `
1133
1158
  padding-inline: var(--iris-nav-item-padding-inline);
1134
1159
  }
1135
1160
 
1161
+ /* A9: the collapsed rail stays centered even though rows carry data-depth
1162
+ (the vertical indent rules would otherwise win the specificity tie). */
1163
+ :where(.iris-nav-menu--collapsed .iris-nav-menu-item[data-depth]) {
1164
+ justify-content: center;
1165
+ padding-inline: var(--iris-nav-item-padding-inline);
1166
+ }
1167
+
1136
1168
  @media (prefers-reduced-motion: reduce) {
1137
1169
  :where(.iris-nav-menu-arrow) {
1138
1170
  transition: none;
1139
1171
  }
1172
+ :where(.iris-nav-menu--vertical .iris-nav-menu-group > .iris-nav-menu-children) {
1173
+ transition: none;
1174
+ }
1140
1175
  }
1141
1176
  `.trim();
1142
1177
  var installed2 = false;
@@ -1155,6 +1190,8 @@ function installNavMenuStyles() {
1155
1190
  }
1156
1191
 
1157
1192
  // src/admin/NavMenu.ts
1193
+ var HOVER_OPEN_DELAY = 100;
1194
+ var HOVER_CLOSE_DELAY = 150;
1158
1195
  var IrisNavMenu = vue.defineComponent({
1159
1196
  name: "IrisNavMenu",
1160
1197
  inheritAttrs: false,
@@ -1186,6 +1223,7 @@ var IrisNavMenu = vue.defineComponent({
1186
1223
  const activePath = vue.computed(
1187
1224
  () => props.activeKey ? core.findNavPath(props.items, props.activeKey).map((n) => n.key) : []
1188
1225
  );
1226
+ const flyoutMode = vue.computed(() => props.orientation === "horizontal" || props.collapsed);
1189
1227
  const isControlled = vue.computed(() => props.expandedKeys !== void 0);
1190
1228
  const model = core.createExpansion({
1191
1229
  mode: "multiple",
@@ -1200,12 +1238,9 @@ var IrisNavMenu = vue.defineComponent({
1200
1238
  const expanded = vue.computed(
1201
1239
  () => isControlled.value ? props.expandedKeys : internalExpanded.value
1202
1240
  );
1203
- vue.watch(
1204
- () => props.activeKey,
1205
- (key) => {
1206
- if (!isControlled.value) model.merge(key ? core.branchTrail(props.items, key) : []);
1207
- }
1208
- );
1241
+ vue.watch([() => props.activeKey, () => props.items], ([key]) => {
1242
+ if (!isControlled.value) model.merge(key ? core.branchTrail(props.items, key) : []);
1243
+ });
1209
1244
  const toggle = (key) => {
1210
1245
  if (isControlled.value) {
1211
1246
  const cur = props.expandedKeys;
@@ -1221,17 +1256,113 @@ var IrisNavMenu = vue.defineComponent({
1221
1256
  const hovered = vue.ref(null);
1222
1257
  const hoveredBranches = vue.ref([]);
1223
1258
  const focusedBranches = vue.ref([]);
1259
+ const clickedBranches = vue.ref([]);
1260
+ let suppressFocusOpen = false;
1261
+ let openTimer = null;
1262
+ const closeTimers = /* @__PURE__ */ new Map();
1263
+ const clearOpenTimer = () => {
1264
+ if (openTimer) {
1265
+ clearTimeout(openTimer);
1266
+ openTimer = null;
1267
+ }
1268
+ };
1269
+ const cancelClose = (key) => {
1270
+ const timer = closeTimers.get(key);
1271
+ if (timer) {
1272
+ clearTimeout(timer);
1273
+ closeTimers.delete(key);
1274
+ }
1275
+ };
1276
+ const cancelAllTimers = () => {
1277
+ clearOpenTimer();
1278
+ for (const timer of closeTimers.values()) clearTimeout(timer);
1279
+ closeTimers.clear();
1280
+ };
1281
+ vue.onBeforeUnmount(cancelAllTimers);
1282
+ const flyoutTriggers = /* @__PURE__ */ new Map();
1283
+ const flyoutPanels = /* @__PURE__ */ new Map();
1284
+ const setFlyoutRef = (map, key, el) => {
1285
+ if (el instanceof HTMLElement) map.set(key, el);
1286
+ else map.delete(key);
1287
+ };
1288
+ const positionFlyout = (key) => {
1289
+ const trigger = flyoutTriggers.get(key);
1290
+ const panel = flyoutPanels.get(key);
1291
+ if (!trigger || !panel || typeof document === "undefined") return;
1292
+ const rect = trigger.getBoundingClientRect();
1293
+ const dir = getComputedStyle(trigger).direction;
1294
+ panel.style.position = "fixed";
1295
+ panel.style.top = `${props.collapsed ? rect.top : rect.bottom}px`;
1296
+ if (dir === "rtl") {
1297
+ panel.style.left = "";
1298
+ panel.style.right = `${window.innerWidth - (props.collapsed ? rect.left : rect.right)}px`;
1299
+ } else {
1300
+ panel.style.right = "";
1301
+ panel.style.left = `${props.collapsed ? rect.right : rect.left}px`;
1302
+ }
1303
+ };
1304
+ vue.onBeforeUnmount(() => {
1305
+ detachViewportListeners();
1306
+ flyoutTriggers.clear();
1307
+ flyoutPanels.clear();
1308
+ });
1309
+ const isolateAtDepth = (key) => {
1310
+ const depth = core.findNavPath(props.items, key).length - 1;
1311
+ const isSameDepthOther = (k) => k !== key && core.findNavPath(props.items, k).length - 1 === depth;
1312
+ for (const state of [hoveredBranches, clickedBranches, focusedBranches]) {
1313
+ if (state.value.some(isSameDepthOther)) {
1314
+ state.value = state.value.filter((k) => !isSameDepthOther(k));
1315
+ }
1316
+ }
1317
+ };
1318
+ const scheduleOpen = (key) => {
1319
+ clearOpenTimer();
1320
+ openTimer = setTimeout(() => {
1321
+ openTimer = null;
1322
+ isolateAtDepth(key);
1323
+ setBranchInteraction(hoveredBranches, key, true);
1324
+ }, HOVER_OPEN_DELAY);
1325
+ };
1326
+ const scheduleClose = (key) => {
1327
+ cancelClose(key);
1328
+ closeTimers.set(
1329
+ key,
1330
+ setTimeout(() => {
1331
+ closeTimers.delete(key);
1332
+ setBranchInteraction(hoveredBranches, key, false);
1333
+ }, HOVER_CLOSE_DELAY)
1334
+ );
1335
+ };
1224
1336
  const closeHorizontalMenus = () => {
1225
- if (props.orientation !== "horizontal") return;
1337
+ if (!flyoutMode.value) return;
1338
+ cancelAllTimers();
1226
1339
  hovered.value = null;
1227
1340
  hoveredBranches.value = [];
1228
1341
  focusedBranches.value = [];
1342
+ clickedBranches.value = [];
1229
1343
  };
1230
1344
  const select = (node) => {
1231
1345
  if (node.disabled) return;
1232
1346
  closeHorizontalMenus();
1233
1347
  emit("select", node.key, node);
1234
1348
  };
1349
+ const toggleFlyout = (key) => {
1350
+ cancelAllTimers();
1351
+ const current = clickedBranches.value;
1352
+ if (current.includes(key)) {
1353
+ clickedBranches.value = current.filter((k) => k !== key);
1354
+ setBranchInteraction(hoveredBranches, key, false);
1355
+ setBranchInteraction(focusedBranches, key, false);
1356
+ } else {
1357
+ isolateAtDepth(key);
1358
+ clickedBranches.value = [...clickedBranches.value, key];
1359
+ }
1360
+ };
1361
+ const openFlyout = (key) => {
1362
+ cancelAllTimers();
1363
+ isolateAtDepth(key);
1364
+ setBranchInteraction(focusedBranches, key, true);
1365
+ };
1235
1366
  const setBranchInteraction = (state, key, enabled) => {
1236
1367
  const current = state.value;
1237
1368
  if (enabled) {
@@ -1249,8 +1380,43 @@ var IrisNavMenu = vue.defineComponent({
1249
1380
  const horizontalBranchVisible = (key, depth) => {
1250
1381
  const hoveredAtDepth = interactionKeyAtDepth(hoveredBranches.value, depth);
1251
1382
  if (hoveredAtDepth) return hoveredAtDepth === key;
1383
+ const clickedAtDepth = interactionKeyAtDepth(clickedBranches.value, depth);
1384
+ if (clickedAtDepth) return clickedAtDepth === key;
1252
1385
  return interactionKeyAtDepth(focusedBranches.value, depth) === key;
1253
1386
  };
1387
+ const shownFlyoutKeys = vue.computed(() => {
1388
+ if (!flyoutMode.value) return [];
1389
+ const keys = [];
1390
+ for (const node of tree.value) {
1391
+ if (core.isBranch(node) && horizontalBranchVisible(node.key, 0)) keys.push(node.key);
1392
+ }
1393
+ return keys;
1394
+ });
1395
+ const repositionOpenFlyouts = () => {
1396
+ for (const key of shownFlyoutKeys.value) positionFlyout(key);
1397
+ };
1398
+ let viewportListenersActive = false;
1399
+ const attachViewportListeners = () => {
1400
+ if (viewportListenersActive || typeof document === "undefined") return;
1401
+ viewportListenersActive = true;
1402
+ document.addEventListener("scroll", repositionOpenFlyouts, true);
1403
+ window.addEventListener("resize", repositionOpenFlyouts);
1404
+ };
1405
+ const detachViewportListeners = () => {
1406
+ if (!viewportListenersActive || typeof document === "undefined") return;
1407
+ viewportListenersActive = false;
1408
+ document.removeEventListener("scroll", repositionOpenFlyouts, true);
1409
+ window.removeEventListener("resize", repositionOpenFlyouts);
1410
+ };
1411
+ vue.watch(
1412
+ shownFlyoutKeys,
1413
+ (keys, prev) => {
1414
+ if (keys.length > 0 && prev.length === 0) attachViewportListeners();
1415
+ else if (keys.length === 0 && prev.length > 0) detachViewportListeners();
1416
+ for (const key of keys) positionFlyout(key);
1417
+ },
1418
+ { flush: "post" }
1419
+ );
1254
1420
  const itemClass = (opts) => {
1255
1421
  const depth = Math.min(9, opts.depth);
1256
1422
  return [
@@ -1273,81 +1439,47 @@ var IrisNavMenu = vue.defineComponent({
1273
1439
  },
1274
1440
  String(node.badge)
1275
1441
  ) : null;
1276
- const renderCollapsed = () => tree.value.map((node) => {
1442
+ const renderItem = (node, depth) => {
1277
1443
  const branch = core.isBranch(node);
1278
- const active = branch ? activePath.value.includes(node.key) : node.key === props.activeKey;
1279
- return vue.h(
1444
+ const horizontal = flyoutMode.value;
1445
+ const open = expanded.value.includes(node.key);
1446
+ const shown = horizontal ? horizontalBranchVisible(node.key, depth) : open;
1447
+ const active = node.key === props.activeKey;
1448
+ const trail = branch && !active && activePath.value.includes(node.key);
1449
+ const rowHovered = hovered.value === node.key || horizontal && shown;
1450
+ const arrowReversed = branch && !node.disabled && (active || trail || shown || rowHovered);
1451
+ const isFlyoutRoot = horizontal && branch && depth === 0;
1452
+ const row = vue.h(
1280
1453
  "button",
1281
1454
  {
1282
- key: node.key,
1283
1455
  type: "button",
1456
+ ref: (el) => {
1457
+ if (isFlyoutRoot) setFlyoutRef(flyoutTriggers, node.key, el);
1458
+ },
1284
1459
  class: [
1285
1460
  ...itemClass({
1286
- depth: 0,
1461
+ depth,
1287
1462
  active,
1288
- trail: false,
1289
- hovered: hovered.value === node.key,
1290
- open: false,
1463
+ trail,
1464
+ hovered: rowHovered,
1465
+ open: branch ? shown : false,
1291
1466
  disabled: !!node.disabled,
1292
1467
  branch
1293
1468
  }),
1294
- "is-collapsed"
1469
+ ...props.collapsed && depth === 0 ? ["is-collapsed"] : []
1295
1470
  ],
1296
1471
  "data-iris-nav-item": "",
1297
1472
  "data-key": node.key,
1298
- "data-active": active ? "true" : void 0,
1299
- "data-branch": branch ? "true" : void 0,
1300
- "data-hovered": hovered.value === node.key ? "true" : void 0,
1301
- disabled: node.disabled,
1302
- title: node.title,
1303
- "aria-label": branch ? `${node.title} (section)` : node.title,
1304
- "aria-current": active && !branch ? "page" : void 0,
1305
- onMouseenter: () => {
1306
- if (!node.disabled) hovered.value = node.key;
1307
- },
1308
- onMouseleave: (event) => {
1309
- if (hovered.value === node.key && !keepsPointerInside(event.currentTarget, event)) {
1310
- hovered.value = null;
1311
- }
1312
- },
1313
- onClick: () => select(branch ? core.firstLeaf(node) : node)
1314
- },
1315
- [
1316
- iconNode(node, 20) ?? vue.h("span", { class: "iris-nav-menu-fallback-icon" }, node.title.charAt(0))
1317
- ]
1318
- );
1319
- });
1320
- const renderItem = (node, depth) => {
1321
- const branch = core.isBranch(node);
1322
- const open = expanded.value.includes(node.key);
1323
- const horizontal = props.orientation === "horizontal";
1324
- const shown = horizontal ? horizontalBranchVisible(node.key, depth) : open;
1325
- const active = node.key === props.activeKey;
1326
- const trail = branch && !active && activePath.value.includes(node.key);
1327
- const arrowReversed = branch && !node.disabled && (active || trail || shown || hovered.value === node.key);
1328
- const row = vue.h(
1329
- "button",
1330
- {
1331
- type: "button",
1332
- class: itemClass({
1333
- depth,
1334
- active,
1335
- trail,
1336
- hovered: hovered.value === node.key,
1337
- open: branch ? shown : false,
1338
- disabled: !!node.disabled,
1339
- branch
1340
- }),
1341
- "data-iris-nav-item": "",
1342
- "data-key": node.key,
1343
1473
  "data-branch": branch ? "true" : void 0,
1344
1474
  "data-active": active ? "true" : void 0,
1345
1475
  "data-active-trail": trail ? "true" : void 0,
1346
1476
  "data-open": branch && shown ? "true" : void 0,
1347
- "data-hovered": hovered.value === node.key ? "true" : void 0,
1477
+ "data-hovered": rowHovered ? "true" : void 0,
1348
1478
  "data-depth": String(depth),
1349
1479
  "data-orientation": props.orientation,
1350
1480
  disabled: node.disabled,
1481
+ title: props.collapsed ? node.title : void 0,
1482
+ "aria-label": props.collapsed ? branch ? `${node.title} (section)` : node.title : void 0,
1351
1483
  "aria-expanded": branch ? shown ? "true" : "false" : void 0,
1352
1484
  "aria-current": active ? "page" : void 0,
1353
1485
  onMouseenter: () => {
@@ -1358,7 +1490,17 @@ var IrisNavMenu = vue.defineComponent({
1358
1490
  hovered.value = null;
1359
1491
  }
1360
1492
  },
1361
- onClick: () => branch ? toggle(node.key) : select(node)
1493
+ onClick: () => {
1494
+ if (props.collapsed && branch) {
1495
+ select(core.firstLeaf(node));
1496
+ } else if (horizontal && branch) {
1497
+ toggleFlyout(node.key);
1498
+ } else if (branch) {
1499
+ toggle(node.key);
1500
+ } else {
1501
+ select(node);
1502
+ }
1503
+ }
1362
1504
  },
1363
1505
  [
1364
1506
  iconNode(node),
@@ -1370,54 +1512,49 @@ var IrisNavMenu = vue.defineComponent({
1370
1512
  node.title
1371
1513
  ),
1372
1514
  badgeNode(node),
1373
- branch ? vue.h(IrisIcon, {
1374
- name: horizontal && depth === 0 ? "chevron-down" : "chevron-right",
1515
+ branch && !(props.collapsed && depth === 0) ? vue.h(IrisIcon, {
1516
+ name: horizontal && depth === 0 && !props.collapsed ? "chevron-down" : "chevron-right",
1375
1517
  size: 16,
1376
1518
  class: "iris-nav-menu-arrow",
1377
1519
  "data-reversed": arrowReversed ? "true" : void 0
1378
1520
  }) : null
1379
1521
  ]
1380
1522
  );
1381
- if (!branch || !horizontal && !open)
1382
- return vue.h(
1383
- "div",
1384
- {
1385
- key: node.key,
1386
- "data-iris-nav-group": branch ? "" : void 0,
1387
- class: ["iris-nav-menu-group", `iris-nav-menu-group--depth-${Math.min(9, depth)}`]
1388
- },
1389
- [row]
1390
- );
1391
1523
  return vue.h(
1392
1524
  "div",
1393
1525
  {
1394
1526
  key: node.key,
1395
- "data-iris-nav-group": "",
1396
- "data-open": shown ? "true" : void 0,
1527
+ "data-iris-nav-group": branch ? "" : void 0,
1528
+ "data-open": branch && shown ? "true" : void 0,
1397
1529
  "data-depth": String(depth),
1398
1530
  class: ["iris-nav-menu-group", `iris-nav-menu-group--depth-${Math.min(9, depth)}`],
1399
- ...horizontal ? {
1531
+ ...horizontal && branch ? {
1400
1532
  onMouseenter: (event) => {
1401
- if (!keepsPointerInside(event.currentTarget, event)) {
1402
- setBranchInteraction(hoveredBranches, node.key, true);
1533
+ if (!node.disabled && !keepsPointerInside(event.currentTarget, event)) {
1534
+ cancelClose(node.key);
1535
+ scheduleOpen(node.key);
1403
1536
  }
1404
1537
  },
1405
1538
  onMouseover: (event) => {
1406
- if (!keepsPointerInside(event.currentTarget, event)) {
1407
- setBranchInteraction(hoveredBranches, node.key, true);
1539
+ if (!node.disabled && !keepsPointerInside(event.currentTarget, event)) {
1540
+ cancelClose(node.key);
1541
+ scheduleOpen(node.key);
1408
1542
  }
1409
1543
  },
1410
1544
  onMouseout: (event) => {
1411
- if (!keepsPointerInside(event.currentTarget, event)) {
1412
- setBranchInteraction(hoveredBranches, node.key, false);
1545
+ if (!node.disabled && !keepsPointerInside(event.currentTarget, event)) {
1546
+ scheduleClose(node.key);
1413
1547
  }
1414
1548
  },
1415
1549
  onMouseleave: (event) => {
1416
- if (!keepsPointerInside(event.currentTarget, event)) {
1417
- setBranchInteraction(hoveredBranches, node.key, false);
1550
+ if (!node.disabled && !keepsPointerInside(event.currentTarget, event)) {
1551
+ scheduleClose(node.key);
1418
1552
  }
1419
1553
  },
1420
- onFocusin: () => setBranchInteraction(focusedBranches, node.key, true),
1554
+ onFocusin: () => {
1555
+ if (suppressFocusOpen) return;
1556
+ setBranchInteraction(focusedBranches, node.key, true);
1557
+ },
1421
1558
  onFocusout: (event) => {
1422
1559
  const group = event.currentTarget;
1423
1560
  const next = event.relatedTarget;
@@ -1429,19 +1566,45 @@ var IrisNavMenu = vue.defineComponent({
1429
1566
  },
1430
1567
  [
1431
1568
  row,
1432
- vue.h(
1569
+ branch ? vue.h(
1433
1570
  "div",
1434
1571
  {
1435
1572
  class: "iris-nav-menu-children",
1436
1573
  "data-iris-nav-children": "",
1574
+ ref: (el) => {
1575
+ if (isFlyoutRoot) setFlyoutRef(flyoutPanels, node.key, el);
1576
+ },
1437
1577
  role: "group",
1438
- "aria-hidden": horizontal && !shown ? "true" : void 0
1578
+ "aria-hidden": shown ? void 0 : "true"
1439
1579
  },
1440
- (node.children ?? []).map((child) => renderItem(child, depth + 1))
1441
- )
1580
+ [
1581
+ vue.h(
1582
+ "div",
1583
+ { class: "iris-nav-menu-children-inner" },
1584
+ (node.children ?? []).map((child) => renderItem(child, depth + 1))
1585
+ )
1586
+ ]
1587
+ ) : null
1442
1588
  ]
1443
1589
  );
1444
1590
  };
1591
+ const rootRef = vue.shallowRef(null);
1592
+ vue.watch(
1593
+ [() => props.activeKey, () => props.items],
1594
+ () => {
1595
+ if (!props.activeKey) return;
1596
+ const root = rootRef.value;
1597
+ if (!root) return;
1598
+ const el = root.querySelector('[data-iris-nav-item][data-active="true"]');
1599
+ if (!el || typeof el.scrollIntoView !== "function") return;
1600
+ let behavior = "auto";
1601
+ if (typeof window !== "undefined" && typeof window.matchMedia === "function" && !window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
1602
+ behavior = "smooth";
1603
+ }
1604
+ el.scrollIntoView({ block: "nearest", inline: "nearest", behavior });
1605
+ },
1606
+ { flush: "post" }
1607
+ );
1445
1608
  const onKeydown = (e) => {
1446
1609
  const root = e.currentTarget;
1447
1610
  const buttons = Array.from(root.querySelectorAll("[data-iris-nav-item]")).filter(
@@ -1450,15 +1613,33 @@ var IrisNavMenu = vue.defineComponent({
1450
1613
  if (buttons.length === 0) return;
1451
1614
  const idx = buttons.indexOf(document.activeElement);
1452
1615
  const focusAt = (i) => buttons[(i + buttons.length) % buttons.length]?.focus();
1616
+ const key = idx >= 0 ? buttons[idx]?.getAttribute("data-key") : void 0;
1617
+ const node = key ? core.findNavNode(props.items, key) : void 0;
1618
+ const depth = idx >= 0 ? Number(buttons[idx]?.getAttribute("data-depth") ?? 0) : 0;
1453
1619
  switch (e.key) {
1454
- case "ArrowDown":
1620
+ case "ArrowDown": {
1455
1621
  e.preventDefault();
1622
+ if (flyoutMode.value && node && core.isBranch(node)) {
1623
+ openFlyout(key);
1624
+ const group = buttons[idx].closest("[data-iris-nav-group]");
1625
+ void vue.nextTick(() => {
1626
+ group?.querySelector("[data-iris-nav-children] [data-iris-nav-item]")?.focus();
1627
+ });
1628
+ return;
1629
+ }
1456
1630
  focusAt(idx < 0 ? 0 : idx + 1);
1457
1631
  return;
1458
- case "ArrowUp":
1632
+ }
1633
+ case "ArrowUp": {
1459
1634
  e.preventDefault();
1635
+ if (flyoutMode.value && node && core.isBranch(node) && horizontalBranchVisible(key, depth)) {
1636
+ setBranchInteraction(focusedBranches, key, false);
1637
+ setBranchInteraction(clickedBranches, key, false);
1638
+ return;
1639
+ }
1460
1640
  focusAt(idx < 0 ? buttons.length - 1 : idx - 1);
1461
1641
  return;
1642
+ }
1462
1643
  case "Home":
1463
1644
  e.preventDefault();
1464
1645
  buttons[0]?.focus();
@@ -1467,23 +1648,56 @@ var IrisNavMenu = vue.defineComponent({
1467
1648
  e.preventDefault();
1468
1649
  buttons[buttons.length - 1]?.focus();
1469
1650
  return;
1651
+ case "Escape": {
1652
+ if (flyoutMode.value) {
1653
+ const openKeys = [
1654
+ ...hoveredBranches.value,
1655
+ ...clickedBranches.value,
1656
+ ...focusedBranches.value
1657
+ ];
1658
+ if (openKeys.length > 0) {
1659
+ e.preventDefault();
1660
+ const openTop = openKeys.find((k) => core.findNavPath(props.items, k).length === 1);
1661
+ closeHorizontalMenus();
1662
+ if (openTop) {
1663
+ suppressFocusOpen = true;
1664
+ buttons.find((b) => b.getAttribute("data-key") === openTop)?.focus();
1665
+ void vue.nextTick(() => {
1666
+ suppressFocusOpen = false;
1667
+ });
1668
+ }
1669
+ }
1670
+ }
1671
+ return;
1672
+ }
1470
1673
  }
1471
- if (props.collapsed || idx < 0) return;
1472
- const key = buttons[idx]?.getAttribute("data-key");
1473
- if (!key) return;
1474
- const node = core.findNavNode(props.items, key);
1475
- if (e.key === "ArrowRight" && node && core.isBranch(node)) {
1674
+ if (props.collapsed || idx < 0 || !key || !node) return;
1675
+ if (e.key === "ArrowRight") {
1476
1676
  e.preventDefault();
1477
- if (!expanded.value.includes(key)) toggle(key);
1478
- else focusAt(idx + 1);
1677
+ if (core.isBranch(node)) {
1678
+ if (flyoutMode.value || !expanded.value.includes(key)) {
1679
+ if (flyoutMode.value) openFlyout(key);
1680
+ else toggle(key);
1681
+ const group = buttons[idx].closest("[data-iris-nav-group]");
1682
+ void vue.nextTick(() => {
1683
+ group?.querySelector("[data-iris-nav-children] [data-iris-nav-item]")?.focus();
1684
+ });
1685
+ } else {
1686
+ focusAt(idx + 1);
1687
+ }
1688
+ }
1479
1689
  } else if (e.key === "ArrowLeft") {
1480
- if (node && core.isBranch(node) && expanded.value.includes(key)) {
1481
- e.preventDefault();
1690
+ e.preventDefault();
1691
+ if (flyoutMode.value && core.isBranch(node)) {
1692
+ setBranchInteraction(focusedBranches, key, false);
1693
+ setBranchInteraction(clickedBranches, key, false);
1694
+ return;
1695
+ }
1696
+ if (core.isBranch(node) && expanded.value.includes(key)) {
1482
1697
  toggle(key);
1483
1698
  } else {
1484
1699
  const parentKey = core.findNavPath(props.items, key).at(-2)?.key;
1485
1700
  if (parentKey) {
1486
- e.preventDefault();
1487
1701
  buttons.find((b) => b.getAttribute("data-key") === parentKey)?.focus();
1488
1702
  }
1489
1703
  }
@@ -1498,6 +1712,9 @@ var IrisNavMenu = vue.defineComponent({
1498
1712
  "nav",
1499
1713
  {
1500
1714
  ...attrs,
1715
+ ref: (el) => {
1716
+ rootRef.value = el ?? null;
1717
+ },
1501
1718
  class: [...menuClass, attrs.class],
1502
1719
  "data-iris-nav-menu": "",
1503
1720
  "data-collapsed": props.collapsed ? "true" : void 0,
@@ -1506,7 +1723,7 @@ var IrisNavMenu = vue.defineComponent({
1506
1723
  onKeydown,
1507
1724
  style: attrs.style
1508
1725
  },
1509
- props.collapsed ? renderCollapsed() : tree.value.map((node) => renderItem(node, 0))
1726
+ tree.value.map((node) => renderItem(node, 0))
1510
1727
  );
1511
1728
  }
1512
1729
  });
@@ -1576,11 +1793,11 @@ var IrisBreadcrumb = vue.defineComponent({
1576
1793
  style: {
1577
1794
  display: "inline-flex",
1578
1795
  alignItems: "center",
1579
- gap: "6px",
1796
+ gap: "var(--iris-space-xs, 8px)",
1580
1797
  margin: "0",
1581
1798
  padding: "0",
1582
1799
  listStyle: "none",
1583
- fontSize: "14px"
1800
+ fontSize: "var(--iris-font-size-md, 14px)"
1584
1801
  }
1585
1802
  },
1586
1803
  items
@@ -1896,6 +2113,7 @@ var IrisDropdownTrigger = vue.defineComponent({
1896
2113
  function useFloating(options) {
1897
2114
  const x = vue.ref(0);
1898
2115
  const y = vue.ref(0);
2116
+ const positioned = vue.ref(false);
1899
2117
  const strategy = vue.ref(options.strategy ?? "absolute");
1900
2118
  const finalPlacement = vue.ref(options.placement ?? "bottom");
1901
2119
  const arrowX = vue.ref();
@@ -1941,6 +2159,7 @@ function useFloating(options) {
1941
2159
  if (token !== epoch) return;
1942
2160
  x.value = result.x;
1943
2161
  y.value = result.y;
2162
+ positioned.value = true;
1944
2163
  finalPlacement.value = result.placement;
1945
2164
  const arrowData = result.middlewareData.arrow;
1946
2165
  arrowX.value = arrowData?.x;
@@ -1953,6 +2172,7 @@ function useFloating(options) {
1953
2172
  cleanupAutoUpdate = null;
1954
2173
  epoch++;
1955
2174
  if (isOpen && anchorEl && floatingEl) {
2175
+ positioned.value = false;
1956
2176
  cleanupAutoUpdate = dom.autoUpdate(anchorEl, floatingEl, () => {
1957
2177
  void update();
1958
2178
  });
@@ -1979,7 +2199,11 @@ function useFloating(options) {
1979
2199
  top: "0",
1980
2200
  left: "0",
1981
2201
  transform: `translate3d(${Math.round(x.value)}px, ${Math.round(y.value)}px, 0)`,
1982
- width: "max-content"
2202
+ width: "max-content",
2203
+ // Hidden until the first positioning cycle lands — prevents a flash at
2204
+ // the viewport origin. visibility (not display:none) keeps the panel
2205
+ // measurable so Floating UI can compute its real coordinates.
2206
+ visibility: positioned.value ? "visible" : "hidden"
1983
2207
  }));
1984
2208
  return { x, y, strategy, finalPlacement, floatingStyles, update, arrowX, arrowY, arrowSide };
1985
2209
  }
@@ -1998,6 +2222,7 @@ function useDismiss(options) {
1998
2222
  const onPointerDown = (event) => {
1999
2223
  if (!options.enabled.value) return;
2000
2224
  if (isExcluded(event.target)) return;
2225
+ if (options.excludePredicate?.(event.target)) return;
2001
2226
  options.onDismiss();
2002
2227
  };
2003
2228
  const onKeyDown = (event) => {
@@ -2145,12 +2370,12 @@ var IrisDropdownMenu = vue.defineComponent({
2145
2370
  onKeydown: onKeyDown,
2146
2371
  style: {
2147
2372
  ...floatingStyles.value,
2148
- background: "var(--iris-surface)",
2373
+ background: "var(--iris-surface-floating)",
2149
2374
  color: "var(--iris-foreground)",
2150
2375
  border: "1px solid var(--iris-border)",
2151
2376
  borderRadius: "var(--iris-radius-md)",
2152
2377
  padding: "var(--iris-padding-sm)",
2153
- boxShadow: "0 8px 24px -8px rgba(0, 0, 0, 0.16), 0 4px 8px -2px rgba(0, 0, 0, 0.08)",
2378
+ boxShadow: "var(--iris-shadow-lg)",
2154
2379
  minWidth: "160px",
2155
2380
  outline: "none",
2156
2381
  zIndex: "1000",
@@ -2215,14 +2440,14 @@ var IrisDropdownItem = vue.defineComponent({
2215
2440
  display: "flex",
2216
2441
  alignItems: "center",
2217
2442
  gap: "var(--iris-gap-sm)",
2218
- padding: "6px var(--iris-padding-md)",
2443
+ padding: "var(--iris-padding-sm, 6px) var(--iris-padding-md)",
2219
2444
  borderRadius: "var(--iris-radius-sm)",
2220
2445
  cursor: props.disabled ? "not-allowed" : "pointer",
2221
2446
  opacity: props.disabled ? "0.5" : "1",
2222
2447
  background: hovered.value && !props.disabled ? "var(--iris-surface-hover)" : "transparent",
2223
2448
  color: "inherit",
2224
2449
  outline: "none",
2225
- fontSize: "14px",
2450
+ fontSize: "var(--iris-font-size-md, 14px)",
2226
2451
  transition: "background-color 80ms ease",
2227
2452
  ...attrs.style ?? {}
2228
2453
  }
@@ -2497,12 +2722,12 @@ var IrisAdminTabs = vue.defineComponent({
2497
2722
  style: {
2498
2723
  display: "inline-flex",
2499
2724
  alignItems: "center",
2500
- gap: "6px",
2725
+ gap: "var(--iris-space-xs, 8px)",
2501
2726
  border: "none",
2502
2727
  background: "transparent",
2503
2728
  color: "inherit",
2504
2729
  font: "inherit",
2505
- fontSize: "13px",
2730
+ fontSize: "var(--iris-font-size-sm, 13px)",
2506
2731
  cursor: "pointer",
2507
2732
  padding: "0"
2508
2733
  },
@@ -2552,7 +2777,7 @@ var IrisAdminTabs = vue.defineComponent({
2552
2777
  display: "inline-flex",
2553
2778
  alignItems: "center",
2554
2779
  gap: "4px",
2555
- padding: "5px 8px 5px 10px",
2780
+ padding: "var(--iris-padding-sm, 6px) var(--iris-space-xs, 8px) var(--iris-padding-sm, 6px) var(--iris-space-sm, 12px)",
2556
2781
  borderRadius: "var(--iris-radius-md, 6px)",
2557
2782
  border: `1px solid ${active ? "var(--iris-primary)" : "var(--iris-border)"}`,
2558
2783
  background: active ? "var(--iris-primary)" : "var(--iris-surface)",
@@ -2568,27 +2793,23 @@ var IrisAdminTabs = vue.defineComponent({
2568
2793
  default: () => [
2569
2794
  vue.h(
2570
2795
  IrisDropdownTrigger,
2571
- { "aria-label": tr("admin.tabActions") },
2572
2796
  {
2573
- default: () => vue.h(
2574
- "span",
2575
- {
2576
- "data-iris-tab-actions": "",
2577
- style: {
2578
- display: "inline-flex",
2579
- alignItems: "center",
2580
- justifyContent: "center",
2581
- width: "28px",
2582
- height: "28px",
2583
- borderRadius: "var(--iris-radius-md, 6px)",
2584
- border: "1px solid var(--iris-border)",
2585
- background: "var(--iris-surface)",
2586
- color: "var(--iris-foreground)",
2587
- cursor: "pointer"
2588
- }
2589
- },
2590
- [vue.h(IrisIcon, { name: "more-horizontal", size: 16 })]
2591
- )
2797
+ "aria-label": tr("admin.tabActions"),
2798
+ style: {
2799
+ display: "inline-flex",
2800
+ alignItems: "center",
2801
+ justifyContent: "center",
2802
+ width: "28px",
2803
+ height: "28px",
2804
+ borderRadius: "var(--iris-radius-md, 6px)",
2805
+ border: "1px solid var(--iris-border)",
2806
+ background: "var(--iris-surface)",
2807
+ color: "var(--iris-foreground)",
2808
+ cursor: "pointer"
2809
+ }
2810
+ },
2811
+ {
2812
+ default: () => vue.h(IrisIcon, { name: "more-horizontal", size: 16 })
2592
2813
  }
2593
2814
  ),
2594
2815
  vue.h(IrisDropdownMenu, null, {
@@ -2617,8 +2838,8 @@ var IrisAdminTabs = vue.defineComponent({
2617
2838
  style: {
2618
2839
  display: "flex",
2619
2840
  alignItems: "center",
2620
- gap: "6px",
2621
- padding: "6px 10px",
2841
+ gap: "var(--iris-space-xs, 8px)",
2842
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
2622
2843
  borderBottom: "1px solid var(--iris-border)",
2623
2844
  background: "var(--iris-background)",
2624
2845
  ...attrs.style ?? {}
@@ -2648,7 +2869,7 @@ var IrisAdminTabs = vue.defineComponent({
2648
2869
  orientation: "horizontal",
2649
2870
  style: {
2650
2871
  alignItems: "center",
2651
- gap: "6px",
2872
+ gap: "var(--iris-space-xs, 8px)",
2652
2873
  width: "max-content"
2653
2874
  }
2654
2875
  },
@@ -2775,12 +2996,12 @@ var IrisAdminLayout = vue.defineComponent({
2775
2996
  style: {
2776
2997
  display: "flex",
2777
2998
  alignItems: "center",
2778
- gap: "10px",
2999
+ gap: "var(--iris-space-sm, 12px)",
2779
3000
  height: "52px",
2780
- padding: "0 16px",
3001
+ padding: "0 var(--iris-space-md, 16px)",
2781
3002
  color: "var(--iris-foreground)",
2782
3003
  fontWeight: "700",
2783
- fontSize: "16px",
3004
+ fontSize: "var(--iris-font-size-lg, 16px)",
2784
3005
  whiteSpace: "nowrap",
2785
3006
  overflow: "hidden",
2786
3007
  flexShrink: 0
@@ -3326,7 +3547,10 @@ var CSS3 = `
3326
3547
  opacity: 0.6;
3327
3548
  }
3328
3549
  .iris-button[data-iris-button-variant="solid"]:not([disabled]):not([aria-busy="true"]):hover {
3329
- filter: brightness(1.08);
3550
+ background: color-mix(in srgb, var(--iris-danger) 92%, var(--iris-surface));
3551
+ }
3552
+ .iris-button:not([disabled]):not([aria-busy="true"]):active {
3553
+ transform: scale(0.98);
3330
3554
  }
3331
3555
  .iris-button[data-iris-button-variant="outline"]:not([disabled]):not([aria-busy="true"]):hover,
3332
3556
  .iris-button[data-iris-button-variant="ghost"]:not([disabled]):not([aria-busy="true"]):hover {
@@ -3368,15 +3592,15 @@ function installButtonStyles() {
3368
3592
  var SIZE_STYLES = {
3369
3593
  sm: {
3370
3594
  padding: "var(--iris-padding-sm) var(--iris-padding-md)",
3371
- fontSize: "12px"
3595
+ fontSize: "var(--iris-font-size-xs, 12px)"
3372
3596
  },
3373
3597
  md: {
3374
3598
  padding: "var(--iris-padding-sm) var(--iris-padding-lg)",
3375
- fontSize: "14px"
3599
+ fontSize: "var(--iris-font-size-md, 14px)"
3376
3600
  },
3377
3601
  lg: {
3378
3602
  padding: "var(--iris-padding-md) var(--iris-padding-lg)",
3379
- fontSize: "16px"
3603
+ fontSize: "var(--iris-font-size-lg, 16px)"
3380
3604
  }
3381
3605
  };
3382
3606
  var VARIANT_STYLES = {
@@ -3400,6 +3624,11 @@ var VARIANT_STYLES = {
3400
3624
  color: "var(--iris-primary)",
3401
3625
  border: "1px solid transparent",
3402
3626
  textDecoration: "none"
3627
+ },
3628
+ danger: {
3629
+ background: "var(--iris-danger)",
3630
+ color: "var(--iris-on-color, #ffffff)",
3631
+ border: "1px solid var(--iris-danger)"
3403
3632
  }
3404
3633
  };
3405
3634
  function buildInlineStyle(variant, size) {
@@ -3620,6 +3849,74 @@ var IrisPopoverTrigger = vue.defineComponent({
3620
3849
  };
3621
3850
  }
3622
3851
  });
3852
+
3853
+ // src/primitives/floating/animations.ts
3854
+ var STYLE_ID2 = "iris-floating-animations";
3855
+ var installed4 = false;
3856
+ var ANIM_DIALOG = "var(--iris-anim-dialog)";
3857
+ var ANIM_POPOVER = "var(--iris-anim-popover)";
3858
+ var ANIM_TOAST = "var(--iris-anim-toast)";
3859
+ var ANIM_TOOLTIP = "var(--iris-anim-tooltip)";
3860
+ function installFloatingAnimations() {
3861
+ if (installed4 || typeof document === "undefined") return;
3862
+ installed4 = true;
3863
+ if (document.getElementById(STYLE_ID2)) return;
3864
+ const style = document.createElement("style");
3865
+ style.id = STYLE_ID2;
3866
+ style.textContent = `
3867
+ :root {
3868
+ --iris-anim-dialog: iris-dialog-in 150ms ease-out;
3869
+ --iris-anim-popover: iris-popover-in 150ms ease-out;
3870
+ --iris-anim-toast: iris-toast-in 200ms ease-out;
3871
+ --iris-anim-tooltip: iris-tooltip-in 120ms ease-out;
3872
+ }
3873
+ /*
3874
+ * NOTE: these keyframes animate OPACITY ONLY. The floating layers are
3875
+ * positioned via inline transform: translate3d(...) (Floating UI), and any
3876
+ * transform in the keyframes would override it for the animation duration \u2014
3877
+ * flashing the panel at the viewport origin (0,0) before it jumps into place.
3878
+ * Fade-only keeps the entrance motion without touching positioning.
3879
+ */
3880
+ @keyframes iris-dialog-in {
3881
+ from { opacity: 0; }
3882
+ to { opacity: 1; }
3883
+ }
3884
+ @keyframes iris-popover-in {
3885
+ from { opacity: 0; }
3886
+ to { opacity: 1; }
3887
+ }
3888
+ @keyframes iris-toast-in {
3889
+ from { opacity: 0; }
3890
+ to { opacity: 1; }
3891
+ }
3892
+ @keyframes iris-tooltip-in {
3893
+ from { opacity: 0; }
3894
+ to { opacity: 1; }
3895
+ }
3896
+ @media (prefers-reduced-motion: reduce) {
3897
+ :root {
3898
+ --iris-anim-dialog: none;
3899
+ --iris-anim-popover: none;
3900
+ --iris-anim-toast: none;
3901
+ --iris-anim-tooltip: none;
3902
+ }
3903
+ }
3904
+ /* P4: global focus-visible ring for interactive controls */
3905
+ [data-iris-tabs-trigger]:focus-visible,
3906
+ [data-iris-switch]:focus-visible,
3907
+ [data-iris-checkbox]:focus-visible,
3908
+ [data-iris-select-trigger]:focus-visible,
3909
+ [data-iris-segmented-item]:focus-visible,
3910
+ [data-iris-menu-item]:focus-visible,
3911
+ [data-iris-nav-menu-item]:focus-visible {
3912
+ outline: 2px solid var(--iris-primary);
3913
+ outline-offset: 2px;
3914
+ }
3915
+ `;
3916
+ document.head.appendChild(style);
3917
+ }
3918
+
3919
+ // src/primitives/popover/PopoverContent.ts
3623
3920
  var IrisPopoverContent = vue.defineComponent({
3624
3921
  name: "IrisPopoverContent",
3625
3922
  inheritAttrs: false,
@@ -3631,6 +3928,7 @@ var IrisPopoverContent = vue.defineComponent({
3631
3928
  }
3632
3929
  },
3633
3930
  setup(props, { slots, attrs }) {
3931
+ installFloatingAnimations();
3634
3932
  const ctx = vue.inject(PopoverContextKey);
3635
3933
  if (!ctx) {
3636
3934
  throw new Error("[iris-ui] IrisPopoverContent must be a descendant of IrisPopover");
@@ -3676,13 +3974,14 @@ var IrisPopoverContent = vue.defineComponent({
3676
3974
  "data-placement": ctx.placement,
3677
3975
  style: {
3678
3976
  ...floatingStyles.value,
3679
- background: "var(--iris-surface)",
3977
+ background: "var(--iris-surface-floating)",
3978
+ animation: ANIM_POPOVER,
3680
3979
  color: "var(--iris-foreground)",
3681
3980
  border: "1px solid var(--iris-border)",
3682
3981
  borderRadius: "var(--iris-radius-md)",
3683
3982
  padding: "var(--iris-padding-md)",
3684
- boxShadow: "0 8px 24px -8px rgba(0, 0, 0, 0.16), 0 4px 8px -2px rgba(0, 0, 0, 0.08)",
3685
- fontSize: "14px",
3983
+ boxShadow: "var(--iris-shadow-lg)",
3984
+ fontSize: "var(--iris-font-size-md, 14px)",
3686
3985
  zIndex: "1000",
3687
3986
  outline: "none",
3688
3987
  ...attrs.style ?? {}
@@ -3726,6 +4025,7 @@ var IrisTooltip = vue.defineComponent({
3726
4025
  disabled: { type: Boolean, default: false }
3727
4026
  },
3728
4027
  setup(props, { slots, attrs }) {
4028
+ installFloatingAnimations();
3729
4029
  const triggerRef = vue.ref(null);
3730
4030
  const tooltipRef = vue.ref(null);
3731
4031
  const tooltipId = vue.useId();
@@ -3833,9 +4133,10 @@ var IrisTooltip = vue.defineComponent({
3833
4133
  ...floatingStyles.value,
3834
4134
  background: "var(--iris-foreground)",
3835
4135
  color: "var(--iris-background)",
3836
- padding: "4px 8px",
4136
+ animation: ANIM_TOOLTIP,
4137
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)",
3837
4138
  borderRadius: "var(--iris-radius-sm)",
3838
- fontSize: "12px",
4139
+ fontSize: "var(--iris-font-size-xs, 12px)",
3839
4140
  lineHeight: "1.4",
3840
4141
  maxWidth: "240px",
3841
4142
  pointerEvents: "none",
@@ -4134,6 +4435,7 @@ var IrisDialogContent = vue.defineComponent({
4134
4435
  }
4135
4436
  },
4136
4437
  setup(props, { slots, attrs }) {
4438
+ installFloatingAnimations();
4137
4439
  const ctx = vue.inject(DialogContextKey);
4138
4440
  if (!ctx) {
4139
4441
  throw new Error("[iris-ui] IrisDialogContent must be a descendant of IrisDialog");
@@ -4182,7 +4484,7 @@ var IrisDialogContent = vue.defineComponent({
4182
4484
  style: {
4183
4485
  position: "fixed",
4184
4486
  inset: "0",
4185
- background: "rgba(0, 0, 0, 0.5)",
4487
+ background: "var(--iris-backdrop, rgba(0, 0, 0, 0.5))",
4186
4488
  zIndex: "1200",
4187
4489
  display: "flex",
4188
4490
  alignItems: "center",
@@ -4207,12 +4509,13 @@ var IrisDialogContent = vue.defineComponent({
4207
4509
  "data-state": "open",
4208
4510
  onPointerdown: onContentPointerDown,
4209
4511
  style: {
4210
- background: "var(--iris-surface)",
4512
+ background: "var(--iris-surface-floating)",
4513
+ animation: ANIM_DIALOG,
4211
4514
  color: "var(--iris-foreground)",
4212
4515
  border: "1px solid var(--iris-border)",
4213
4516
  borderRadius: "var(--iris-radius-lg)",
4214
4517
  padding: "var(--iris-padding-lg)",
4215
- boxShadow: "0 24px 48px -16px rgba(0,0,0,0.32), 0 8px 16px -4px rgba(0,0,0,0.16)",
4518
+ boxShadow: "var(--iris-shadow-xl)",
4216
4519
  maxWidth: "90vw",
4217
4520
  maxHeight: "85vh",
4218
4521
  overflow: "auto",
@@ -4247,7 +4550,7 @@ var IrisDialogTitle = vue.defineComponent({
4247
4550
  id: ctx.titleId,
4248
4551
  style: {
4249
4552
  margin: "0 0 var(--iris-gap-md) 0",
4250
- fontSize: "18px",
4553
+ fontSize: "var(--iris-font-size-xl, 18px)",
4251
4554
  fontWeight: "600",
4252
4555
  ...attrs.style ?? {}
4253
4556
  }
@@ -4275,7 +4578,7 @@ var IrisDialogDescription = vue.defineComponent({
4275
4578
  style: {
4276
4579
  margin: "0 0 var(--iris-gap-lg) 0",
4277
4580
  color: "var(--iris-muted)",
4278
- fontSize: "14px",
4581
+ fontSize: "var(--iris-font-size-md, 14px)",
4279
4582
  ...attrs.style ?? {}
4280
4583
  }
4281
4584
  },
@@ -4673,18 +4976,18 @@ var IrisInput = vue.defineComponent({
4673
4976
  const sizeStyles = vue.computed(() => {
4674
4977
  const map = {
4675
4978
  sm: {
4676
- padding: "4px var(--iris-padding-sm)",
4677
- fontSize: "12px",
4979
+ padding: "var(--iris-space-xxs, 4px) var(--iris-padding-sm)",
4980
+ fontSize: "var(--iris-font-size-xs, 12px)",
4678
4981
  minHeight: "28px"
4679
4982
  },
4680
4983
  md: {
4681
- padding: "6px var(--iris-padding-md)",
4682
- fontSize: "14px",
4984
+ padding: "var(--iris-space-xs, 8px) var(--iris-padding-md)",
4985
+ fontSize: "var(--iris-font-size-md, 14px)",
4683
4986
  minHeight: "34px"
4684
4987
  },
4685
4988
  lg: {
4686
- padding: "8px var(--iris-padding-md)",
4687
- fontSize: "16px",
4989
+ padding: "var(--iris-space-xs, 8px) var(--iris-padding-md)",
4990
+ fontSize: "var(--iris-font-size-lg, 16px)",
4688
4991
  minHeight: "40px"
4689
4992
  }
4690
4993
  };
@@ -4692,7 +4995,7 @@ var IrisInput = vue.defineComponent({
4692
4995
  });
4693
4996
  const wrapperStyle = vue.computed(() => {
4694
4997
  const borderColor = props.invalid ? "var(--iris-danger)" : focused.value ? "var(--iris-primary)" : "var(--iris-border)";
4695
- const boxShadow = focused.value ? `0 0 0 3px ${props.invalid ? "rgba(239, 68, 68, 0.18)" : "rgba(99, 102, 241, 0.18)"}` : "none";
4998
+ const boxShadow = focused.value ? `0 0 0 3px ${props.invalid ? "color-mix(in srgb, var(--iris-danger) 18%, transparent)" : "color-mix(in srgb, var(--iris-primary) 18%, transparent)"}` : "none";
4696
4999
  return {
4697
5000
  display: "inline-flex",
4698
5001
  alignItems: "center",
@@ -4813,15 +5116,27 @@ var IrisTextarea = vue.defineComponent({
4813
5116
  const textareaRef = vue.ref(null);
4814
5117
  const sizeStyles = vue.computed(() => {
4815
5118
  const map = {
4816
- sm: { padding: "6px var(--iris-padding-sm)", fontSize: "12px", lineHeight: "1.5" },
4817
- md: { padding: "8px var(--iris-padding-md)", fontSize: "14px", lineHeight: "1.5" },
4818
- lg: { padding: "10px var(--iris-padding-md)", fontSize: "16px", lineHeight: "1.5" }
5119
+ sm: {
5120
+ padding: "var(--iris-space-xs, 8px) var(--iris-padding-sm)",
5121
+ fontSize: "var(--iris-font-size-xs, 12px)",
5122
+ lineHeight: "1.5"
5123
+ },
5124
+ md: {
5125
+ padding: "var(--iris-space-xs, 8px) var(--iris-padding-md)",
5126
+ fontSize: "var(--iris-font-size-md, 14px)",
5127
+ lineHeight: "1.5"
5128
+ },
5129
+ lg: {
5130
+ padding: "var(--iris-space-sm, 12px) var(--iris-padding-md)",
5131
+ fontSize: "var(--iris-font-size-lg, 16px)",
5132
+ lineHeight: "1.5"
5133
+ }
4819
5134
  };
4820
5135
  return map[props.size];
4821
5136
  });
4822
5137
  const wrapperStyle = vue.computed(() => {
4823
5138
  const borderColor = props.invalid ? "var(--iris-danger)" : focused.value ? "var(--iris-primary)" : "var(--iris-border)";
4824
- const boxShadow = focused.value ? `0 0 0 3px ${props.invalid ? "rgba(239, 68, 68, 0.18)" : "rgba(99, 102, 241, 0.18)"}` : "none";
5139
+ const boxShadow = focused.value ? `0 0 0 3px ${props.invalid ? "color-mix(in srgb, var(--iris-danger) 18%, transparent)" : "color-mix(in srgb, var(--iris-primary) 18%, transparent)"}` : "none";
4825
5140
  return {
4826
5141
  display: "flex",
4827
5142
  background: "var(--iris-background)",
@@ -5020,15 +5335,27 @@ var IrisNumberInput = vue.defineComponent({
5020
5335
  };
5021
5336
  const sizeStyles = vue.computed(() => {
5022
5337
  const map = {
5023
- sm: { padding: "4px var(--iris-padding-sm)", fontSize: "12px", minHeight: "28px" },
5024
- md: { padding: "6px var(--iris-padding-md)", fontSize: "14px", minHeight: "34px" },
5025
- lg: { padding: "8px var(--iris-padding-md)", fontSize: "16px", minHeight: "40px" }
5338
+ sm: {
5339
+ padding: "var(--iris-space-xxs, 4px) var(--iris-padding-sm)",
5340
+ fontSize: "var(--iris-font-size-xs, 12px)",
5341
+ minHeight: "28px"
5342
+ },
5343
+ md: {
5344
+ padding: "var(--iris-space-xs, 8px) var(--iris-padding-md)",
5345
+ fontSize: "var(--iris-font-size-md, 14px)",
5346
+ minHeight: "34px"
5347
+ },
5348
+ lg: {
5349
+ padding: "var(--iris-space-xs, 8px) var(--iris-padding-md)",
5350
+ fontSize: "var(--iris-font-size-lg, 16px)",
5351
+ minHeight: "40px"
5352
+ }
5026
5353
  };
5027
5354
  return map[props.size];
5028
5355
  });
5029
5356
  const wrapperStyle = vue.computed(() => {
5030
5357
  const borderColor = props.invalid ? "var(--iris-danger)" : focused.value ? "var(--iris-primary)" : "var(--iris-border)";
5031
- const boxShadow = focused.value ? `0 0 0 3px ${props.invalid ? "rgba(239, 68, 68, 0.18)" : "rgba(99, 102, 241, 0.18)"}` : "none";
5358
+ const boxShadow = focused.value ? `0 0 0 3px ${props.invalid ? "color-mix(in srgb, var(--iris-danger) 18%, transparent)" : "color-mix(in srgb, var(--iris-primary) 18%, transparent)"}` : "none";
5032
5359
  return {
5033
5360
  display: "inline-flex",
5034
5361
  alignItems: "center",
@@ -5137,9 +5464,9 @@ var IrisNumberInput = vue.defineComponent({
5137
5464
  }
5138
5465
  });
5139
5466
  var SIZE_MAP = {
5140
- sm: { box: "32px", height: "36px", fontSize: "14px" },
5141
- md: { box: "38px", height: "44px", fontSize: "18px" },
5142
- lg: { box: "44px", height: "52px", fontSize: "20px" }
5467
+ sm: { box: "32px", height: "36px", fontSize: "var(--iris-font-size-md, 14px)" },
5468
+ md: { box: "38px", height: "44px", fontSize: "var(--iris-font-size-xl, 18px)" },
5469
+ lg: { box: "44px", height: "52px", fontSize: "var(--iris-font-size-2xl, 20px)" }
5143
5470
  };
5144
5471
  var PATTERNS = {
5145
5472
  numeric: /[0-9]/,
@@ -5315,7 +5642,7 @@ var IrisOtpInput = vue.defineComponent({
5315
5642
  borderRadius: "var(--iris-radius-md, 6px)",
5316
5643
  outline: "none",
5317
5644
  opacity: props.disabled ? "0.6" : "1",
5318
- boxShadow: focused ? `0 0 0 3px ${props.invalid ? "rgba(239, 68, 68, 0.18)" : "rgba(99, 102, 241, 0.18)"}` : "none",
5645
+ boxShadow: focused ? `0 0 0 3px ${props.invalid ? "color-mix(in srgb, var(--iris-danger) 18%, transparent)" : "color-mix(in srgb, var(--iris-primary) 18%, transparent)"}` : "none",
5319
5646
  transition: "border-color 120ms ease, box-shadow 120ms ease"
5320
5647
  }
5321
5648
  });
@@ -5487,10 +5814,24 @@ var IrisRating = vue.defineComponent({
5487
5814
  }
5488
5815
  });
5489
5816
  var SIZE_MAP3 = {
5490
- sm: { padding: "4px 8px", fontSize: "12px", minHeight: "28px" },
5491
- md: { padding: "6px 12px", fontSize: "14px", minHeight: "34px" },
5492
- lg: { padding: "8px 12px", fontSize: "16px", minHeight: "40px" }
5817
+ sm: {
5818
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)",
5819
+ fontSize: "var(--iris-font-size-xs, 12px)",
5820
+ minHeight: "28px"
5821
+ },
5822
+ md: {
5823
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
5824
+ fontSize: "var(--iris-font-size-md, 14px)",
5825
+ minHeight: "34px"
5826
+ },
5827
+ lg: {
5828
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
5829
+ fontSize: "var(--iris-font-size-lg, 16px)",
5830
+ minHeight: "40px"
5831
+ }
5493
5832
  };
5833
+ var LISTBOX_MAX_HEIGHT = 240;
5834
+ var ROW_HEIGHT = { sm: 28, md: 34, lg: 40 };
5494
5835
  var IrisCombobox = vue.defineComponent({
5495
5836
  name: "IrisCombobox",
5496
5837
  inheritAttrs: false,
@@ -5502,6 +5843,12 @@ var IrisCombobox = vue.defineComponent({
5502
5843
  disabled: { type: Boolean, default: false },
5503
5844
  invalid: { type: Boolean, default: false },
5504
5845
  size: { type: String, default: "md" },
5846
+ /**
5847
+ * Opt-in windowed rendering of the listbox via the core virtualizer.
5848
+ * When true, only the visible window (+ buffer) of options is rendered;
5849
+ * keyboard navigation scrolls the active option into view. Default false.
5850
+ */
5851
+ virtual: { type: Boolean, default: false },
5505
5852
  /** Text shown when no option matches the query. Defaults to the i18n value. */
5506
5853
  emptyText: { type: String, default: void 0 },
5507
5854
  /** Allow committing free text that matches no option: Enter/blur emits
@@ -5534,10 +5881,53 @@ var IrisCombobox = vue.defineComponent({
5534
5881
  const needle = query.value.trim().toLowerCase();
5535
5882
  return filtering.value && needle ? props.options.filter((o) => o.label.toLowerCase().startsWith(needle)) : props.options;
5536
5883
  });
5884
+ const listboxRef = vue.ref(null);
5885
+ const vstate = vue.ref({
5886
+ items: [],
5887
+ offsetBefore: 0,
5888
+ totalSize: 0,
5889
+ startIndex: 0,
5890
+ endIndex: -1
5891
+ });
5892
+ let v = null;
5893
+ let unsub = null;
5894
+ vue.watchEffect(() => {
5895
+ if (!props.virtual) return;
5896
+ if (!v) {
5897
+ v = core.createVirtualizer({
5898
+ count: 0,
5899
+ estimateSize: () => ROW_HEIGHT[props.size],
5900
+ getItemKey: (i) => filtered.value[i]?.value ?? i,
5901
+ viewportSize: LISTBOX_MAX_HEIGHT,
5902
+ buffer: 4
5903
+ });
5904
+ vstate.value = v.getState();
5905
+ unsub = v.subscribe((s) => {
5906
+ vstate.value = s;
5907
+ });
5908
+ }
5909
+ v.setCount(filtered.value.length);
5910
+ const el = listboxRef.value;
5911
+ if (el) {
5912
+ const max = Math.max(0, v.totalSize() - LISTBOX_MAX_HEIGHT);
5913
+ if (el.scrollTop > max) el.scrollTop = max;
5914
+ }
5915
+ });
5916
+ vue.onBeforeUnmount(() => unsub?.());
5917
+ const ensureVisible = (index) => {
5918
+ if (!props.virtual || !v || index < 0 || index >= filtered.value.length) return;
5919
+ const el = listboxRef.value;
5920
+ if (!el) return;
5921
+ const top = el.scrollTop;
5922
+ const start = index * ROW_HEIGHT[props.size];
5923
+ if (start >= top && start + ROW_HEIGHT[props.size] <= top + LISTBOX_MAX_HEIGHT) return;
5924
+ el.scrollTop = v.scrollToIndex(index, start < top ? "start" : "end");
5925
+ };
5537
5926
  const close = () => {
5538
5927
  open.value = false;
5539
5928
  filtering.value = false;
5540
5929
  activeIndex.value = -1;
5930
+ v?.setScroll(0);
5541
5931
  };
5542
5932
  const selectOption = (opt) => {
5543
5933
  if (opt.disabled) return;
@@ -5569,10 +5959,16 @@ var IrisCombobox = vue.defineComponent({
5569
5959
  activeIndex.value = 0;
5570
5960
  return;
5571
5961
  }
5572
- activeIndex.value = Math.min(list.length - 1, activeIndex.value + 1);
5962
+ const next = Math.min(list.length - 1, activeIndex.value + 1);
5963
+ activeIndex.value = next;
5964
+ ensureVisible(next);
5573
5965
  } else if (event.key === "ArrowUp") {
5574
5966
  event.preventDefault();
5575
- if (open.value) activeIndex.value = Math.max(0, activeIndex.value - 1);
5967
+ if (open.value) {
5968
+ const next = Math.max(0, activeIndex.value - 1);
5969
+ activeIndex.value = next;
5970
+ ensureVisible(next);
5971
+ }
5576
5972
  } else if (event.key === "Enter") {
5577
5973
  if (open.value && activeIndex.value >= 0 && list[activeIndex.value]) {
5578
5974
  event.preventDefault();
@@ -5590,11 +5986,14 @@ var IrisCombobox = vue.defineComponent({
5590
5986
  if (open.value) {
5591
5987
  event.preventDefault();
5592
5988
  activeIndex.value = 0;
5989
+ ensureVisible(0);
5593
5990
  }
5594
5991
  } else if (event.key === "End") {
5595
5992
  if (open.value) {
5596
5993
  event.preventDefault();
5597
- activeIndex.value = list.length - 1;
5994
+ const next = list.length - 1;
5995
+ activeIndex.value = next;
5996
+ ensureVisible(next);
5598
5997
  }
5599
5998
  }
5600
5999
  };
@@ -5670,7 +6069,7 @@ var IrisCombobox = vue.defineComponent({
5670
6069
  borderRadius: "var(--iris-radius-md, 6px)",
5671
6070
  outline: "none",
5672
6071
  opacity: props.disabled ? "0.6" : "1",
5673
- boxShadow: focused.value ? `0 0 0 3px ${props.invalid ? "rgba(239, 68, 68, 0.18)" : "rgba(99, 102, 241, 0.18)"}` : "none",
6072
+ boxShadow: focused.value ? `0 0 0 3px ${props.invalid ? "color-mix(in srgb, var(--iris-danger) 18%, transparent)" : "color-mix(in srgb, var(--iris-primary) 18%, transparent)"}` : "none",
5674
6073
  transition: "border-color 120ms ease, box-shadow 120ms ease"
5675
6074
  }
5676
6075
  }),
@@ -5680,6 +6079,12 @@ var IrisCombobox = vue.defineComponent({
5680
6079
  id: listboxId,
5681
6080
  role: "listbox",
5682
6081
  "data-iris-combobox-listbox": "",
6082
+ ref: (el) => {
6083
+ listboxRef.value = el ?? null;
6084
+ },
6085
+ onScroll: (e) => {
6086
+ v?.setScroll(e.currentTarget.scrollTop);
6087
+ },
5683
6088
  style: {
5684
6089
  position: "absolute",
5685
6090
  insetInlineStart: "0",
@@ -5695,7 +6100,7 @@ var IrisCombobox = vue.defineComponent({
5695
6100
  background: "var(--iris-background)",
5696
6101
  border: "1px solid var(--iris-border)",
5697
6102
  borderRadius: "var(--iris-radius-md, 6px)",
5698
- boxShadow: "0 8px 24px rgba(0,0,0,0.12)"
6103
+ boxShadow: "var(--iris-shadow-lg)"
5699
6104
  }
5700
6105
  },
5701
6106
  list.length === 0 ? [
@@ -5705,13 +6110,65 @@ var IrisCombobox = vue.defineComponent({
5705
6110
  "data-iris-combobox-empty": "",
5706
6111
  "aria-disabled": "true",
5707
6112
  style: {
5708
- padding: "6px 10px",
6113
+ padding: "var(--iris-padding-sm, 6px) var(--iris-space-sm, 12px)",
5709
6114
  color: "var(--iris-muted)",
5710
6115
  fontSize: sz.fontSize
5711
6116
  }
5712
6117
  },
5713
6118
  resolvedEmpty
5714
6119
  )
6120
+ ] : props.virtual && v && vstate.value.items.length > 0 ? [
6121
+ vue.h("li", {
6122
+ role: "presentation",
6123
+ "aria-hidden": "true",
6124
+ "data-iris-combobox-spacer": "",
6125
+ "data-iris-combobox-spacer-type": "top",
6126
+ style: { height: `${vstate.value.offsetBefore}px` }
6127
+ }),
6128
+ ...vstate.value.items.map((item) => {
6129
+ const opt = list[item.index];
6130
+ if (!opt) return null;
6131
+ const isActive = item.index === activeIndex.value;
6132
+ const isSelected = opt.value === props.modelValue;
6133
+ return vue.h(
6134
+ "li",
6135
+ {
6136
+ key: opt.value,
6137
+ id: optionId(item.index),
6138
+ role: "option",
6139
+ "aria-selected": isSelected ? "true" : "false",
6140
+ "aria-disabled": opt.disabled ? "true" : void 0,
6141
+ "aria-setsize": list.length,
6142
+ "aria-posinset": item.index + 1,
6143
+ "data-iris-combobox-option": "",
6144
+ "data-active": isActive ? "true" : void 0,
6145
+ onMousedown: (e) => e.preventDefault(),
6146
+ onMouseenter: () => {
6147
+ activeIndex.value = item.index;
6148
+ },
6149
+ onClick: () => selectOption(opt),
6150
+ style: {
6151
+ padding: "var(--iris-padding-sm, 6px) var(--iris-space-sm, 12px)",
6152
+ fontSize: sz.fontSize,
6153
+ borderRadius: "var(--iris-radius-sm, 4px)",
6154
+ cursor: opt.disabled ? "not-allowed" : "pointer",
6155
+ color: opt.disabled ? "var(--iris-muted)" : "var(--iris-foreground)",
6156
+ background: isActive ? "var(--iris-surface-hover, rgba(99,102,241,0.1))" : "transparent",
6157
+ fontWeight: isSelected ? "600" : "400"
6158
+ }
6159
+ },
6160
+ opt.label
6161
+ );
6162
+ }),
6163
+ vue.h("li", {
6164
+ role: "presentation",
6165
+ "aria-hidden": "true",
6166
+ "data-iris-combobox-spacer": "",
6167
+ "data-iris-combobox-spacer-type": "bottom",
6168
+ style: {
6169
+ height: `${vstate.value.totalSize - vstate.value.offsetBefore - vstate.value.items.length * ROW_HEIGHT[props.size]}px`
6170
+ }
6171
+ })
5715
6172
  ] : list.map((opt, i) => {
5716
6173
  const isActive = i === activeIndex.value;
5717
6174
  const isSelected = opt.value === props.modelValue;
@@ -5731,7 +6188,7 @@ var IrisCombobox = vue.defineComponent({
5731
6188
  },
5732
6189
  onClick: () => selectOption(opt),
5733
6190
  style: {
5734
- padding: "6px 10px",
6191
+ padding: "var(--iris-padding-sm, 6px) var(--iris-space-sm, 12px)",
5735
6192
  fontSize: sz.fontSize,
5736
6193
  borderRadius: "var(--iris-radius-sm, 4px)",
5737
6194
  cursor: opt.disabled ? "not-allowed" : "pointer",
@@ -5812,8 +6269,8 @@ var IrisPasswordInput = vue.defineComponent({
5812
6269
  border: "none",
5813
6270
  cursor: props.disabled ? "not-allowed" : "pointer",
5814
6271
  color: "var(--iris-muted)",
5815
- padding: "0 2px",
5816
- fontSize: "13px",
6272
+ padding: "0 var(--iris-space-xxs, 4px)",
6273
+ fontSize: "var(--iris-font-size-sm, 13px)",
5817
6274
  lineHeight: "1"
5818
6275
  }
5819
6276
  },
@@ -5914,7 +6371,7 @@ var IrisFormField = vue.defineComponent({
5914
6371
  {
5915
6372
  id: hintId,
5916
6373
  "data-iris-form-field-hint": "",
5917
- style: { fontSize: "12px", color: "var(--iris-muted)" }
6374
+ style: { fontSize: "var(--iris-font-size-xs, 12px)", color: "var(--iris-muted)" }
5918
6375
  },
5919
6376
  props.hint
5920
6377
  ) : null,
@@ -5924,7 +6381,10 @@ var IrisFormField = vue.defineComponent({
5924
6381
  id: errorId,
5925
6382
  "data-iris-form-field-error": "",
5926
6383
  role: "alert",
5927
- style: { fontSize: "12px", color: "var(--iris-danger)" }
6384
+ style: {
6385
+ fontSize: "var(--iris-font-size-xs, 12px)",
6386
+ color: "var(--iris-danger)"
6387
+ }
5928
6388
  },
5929
6389
  props.error
5930
6390
  ) : null
@@ -5968,7 +6428,7 @@ var IrisSwitch = vue.defineComponent({
5968
6428
  height: dims.value.height,
5969
6429
  background: props.modelValue ? "var(--iris-primary)" : "var(--iris-border)",
5970
6430
  borderRadius: "999px",
5971
- transition: "background-color 120ms ease",
6431
+ transition: "background-color var(--iris-transition-fast, 150ms) ease",
5972
6432
  cursor: props.disabled ? "not-allowed" : "pointer",
5973
6433
  opacity: props.disabled ? "0.6" : "1",
5974
6434
  verticalAlign: "middle"
@@ -5984,8 +6444,8 @@ var IrisSwitch = vue.defineComponent({
5984
6444
  height: dims.value.thumb,
5985
6445
  background: "var(--iris-background)",
5986
6446
  borderRadius: "999px",
5987
- transition: "left 140ms ease",
5988
- boxShadow: "0 1px 3px rgba(0,0,0,0.18)"
6447
+ transition: "left var(--iris-transition-fast, 150ms) ease",
6448
+ boxShadow: "var(--iris-shadow-sm)"
5989
6449
  };
5990
6450
  });
5991
6451
  const onChange = (event) => {
@@ -6327,7 +6787,7 @@ var LIST_STATE_STYLE = {
6327
6787
  padding: "12px",
6328
6788
  textAlign: "center",
6329
6789
  color: "var(--iris-muted)",
6330
- fontSize: "14px"
6790
+ fontSize: "var(--iris-font-size-md, 14px)"
6331
6791
  };
6332
6792
  var IrisList = vue.defineComponent({
6333
6793
  name: "IrisList",
@@ -6446,13 +6906,14 @@ var IrisList = vue.defineComponent({
6446
6906
  display: "flex",
6447
6907
  alignItems: "center",
6448
6908
  gap: "var(--iris-gap-sm)",
6449
- padding: "6px var(--iris-padding-md)",
6909
+ padding: "var(--iris-padding-sm, 6px) var(--iris-padding-md)",
6450
6910
  borderRadius: "var(--iris-radius-sm)",
6451
6911
  cursor: item.disabled ? "not-allowed" : "pointer",
6452
6912
  opacity: item.disabled ? "0.5" : "1",
6453
- fontSize: "14px",
6454
- background: selected ? "var(--iris-primary)" : hoveredIndex.value === index ? "var(--iris-surface-hover)" : active ? "var(--iris-surface-hover)" : "transparent",
6455
- color: selected ? "var(--iris-primary-foreground)" : "var(--iris-foreground)",
6913
+ fontSize: "var(--iris-font-size-md, 14px)",
6914
+ background: selected ? "var(--iris-surface-selected, rgba(99, 102, 241, 0.12))" : hoveredIndex.value === index ? "var(--iris-surface-hover)" : active ? "var(--iris-surface-hover)" : "transparent",
6915
+ color: "var(--iris-foreground)",
6916
+ fontWeight: selected ? "600" : "400",
6456
6917
  outline: "none"
6457
6918
  };
6458
6919
  const slotContent = slots.item?.({
@@ -6483,7 +6944,28 @@ var IrisList = vue.defineComponent({
6483
6944
  },
6484
6945
  style: baseStyle
6485
6946
  },
6486
- slotContent ?? item.label ?? String(item.value)
6947
+ [
6948
+ vue.h(
6949
+ "span",
6950
+ { style: { flex: "1", minWidth: "0" } },
6951
+ slotContent ?? item.label ?? String(item.value)
6952
+ ),
6953
+ selected ? vue.h(
6954
+ "svg",
6955
+ {
6956
+ "aria-hidden": "true",
6957
+ width: "14",
6958
+ height: "14",
6959
+ viewBox: "0 0 24 24",
6960
+ fill: "none",
6961
+ stroke: "var(--iris-primary)",
6962
+ "stroke-width": "2.5",
6963
+ "stroke-linecap": "round",
6964
+ "stroke-linejoin": "round"
6965
+ },
6966
+ [vue.h("path", { d: "M20 6 9 17l-5-5" })]
6967
+ ) : null
6968
+ ]
6487
6969
  );
6488
6970
  });
6489
6971
  return () => vue.h(
@@ -6507,8 +6989,10 @@ var IrisList = vue.defineComponent({
6507
6989
  padding: "var(--iris-padding-sm)",
6508
6990
  display: "flex",
6509
6991
  flexDirection: "column",
6510
- gap: "2px",
6992
+ gap: "var(--iris-space-xxs, 4px)",
6511
6993
  outline: "none",
6994
+ maxHeight: "240px",
6995
+ overflowY: "auto",
6512
6996
  ...attrs.style ?? {}
6513
6997
  }
6514
6998
  },
@@ -6522,6 +7006,7 @@ var IrisSelect = vue.defineComponent({
6522
7006
  props: {
6523
7007
  items: { type: Array, required: true },
6524
7008
  modelValue: { type: null },
7009
+ multiple: { type: Boolean, default: false },
6525
7010
  defaultValue: { type: null, default: void 0 },
6526
7011
  placeholder: { type: String, default: void 0 },
6527
7012
  size: { type: String, default: "md" },
@@ -6536,7 +7021,13 @@ var IrisSelect = vue.defineComponent({
6536
7021
  teleport: {
6537
7022
  type: [Boolean, Object],
6538
7023
  default: void 0
6539
- }
7024
+ },
7025
+ /**
7026
+ * Opt-in windowed rendering of the listbox via the core virtualizer.
7027
+ * When true, only the visible window (+ buffer) of options is rendered;
7028
+ * keyboard navigation scrolls the active option into view. Default false.
7029
+ */
7030
+ virtual: { type: Boolean, default: false }
6540
7031
  },
6541
7032
  emits: {
6542
7033
  "update:modelValue": (_value) => true,
@@ -6548,25 +7039,157 @@ var IrisSelect = vue.defineComponent({
6548
7039
  const internalValue = vue.ref(props.defaultValue);
6549
7040
  const controlled = vue.computed(() => props.modelValue !== void 0);
6550
7041
  const currentValue = vue.computed(() => controlled.value ? props.modelValue : internalValue.value);
7042
+ const selectedValues = vue.computed(() => {
7043
+ if (!props.multiple) return [];
7044
+ const v2 = currentValue.value;
7045
+ return Array.isArray(v2) ? v2 : v2 !== void 0 ? [v2] : [];
7046
+ });
6551
7047
  const selectedItem = vue.computed(
6552
7048
  () => props.items.find((item) => item.value === currentValue.value) ?? null
6553
7049
  );
6554
7050
  const triggerLabel = vue.computed(() => {
7051
+ if (props.multiple) {
7052
+ const items = props.items.filter((it) => selectedValues.value.includes(it.value));
7053
+ if (items.length === 0) return props.placeholder ?? t("select.placeholder");
7054
+ return items.map((it) => it.label ?? String(it.value)).join(", ");
7055
+ }
6555
7056
  const item = selectedItem.value;
6556
7057
  if (!item) return props.placeholder ?? t("select.placeholder");
6557
7058
  return item.label ?? String(item.value);
6558
7059
  });
6559
7060
  const onSelect = (item) => {
7061
+ if (props.multiple) {
7062
+ const exists = selectedValues.value.includes(item.value);
7063
+ const next = exists ? selectedValues.value.filter((v2) => v2 !== item.value) : [...selectedValues.value, item.value];
7064
+ if (!controlled.value) internalValue.value = next;
7065
+ emit("update:modelValue", next);
7066
+ emit("valueChange", next);
7067
+ return;
7068
+ }
6560
7069
  if (!controlled.value) internalValue.value = item.value;
6561
7070
  emit("update:modelValue", item.value);
6562
7071
  emit("valueChange", item.value);
6563
7072
  open.value = false;
6564
7073
  };
7074
+ const LISTBOX_MAX_HEIGHT3 = 240;
7075
+ const ROW_HEIGHT3 = 36;
7076
+ const listboxRef = vue.ref(null);
7077
+ const vstate = vue.ref({
7078
+ items: [],
7079
+ offsetBefore: 0,
7080
+ totalSize: 0,
7081
+ startIndex: 0,
7082
+ endIndex: -1
7083
+ });
7084
+ let v = null;
7085
+ let vUnsub = null;
7086
+ const nav = vue.ref(null);
7087
+ const activeIndex = vue.ref(-1);
7088
+ const isEnabled = (i) => !props.items[i]?.disabled;
7089
+ vue.watchEffect(() => {
7090
+ if (!props.virtual) return;
7091
+ if (!v) {
7092
+ v = core.createVirtualizer({
7093
+ count: 0,
7094
+ estimateSize: () => ROW_HEIGHT3,
7095
+ getItemKey: (i) => String(props.items[i]?.value ?? i),
7096
+ viewportSize: LISTBOX_MAX_HEIGHT3,
7097
+ buffer: 4
7098
+ });
7099
+ vstate.value = v.getState();
7100
+ vUnsub = v.subscribe((s) => {
7101
+ vstate.value = s;
7102
+ });
7103
+ nav.value = core.createKeyboardNav({
7104
+ count: props.items.length,
7105
+ loop: true,
7106
+ isEnabled
7107
+ });
7108
+ activeIndex.value = nav.value.index;
7109
+ }
7110
+ v.setCount(props.items.length);
7111
+ const el = listboxRef.value;
7112
+ if (el) {
7113
+ const max = Math.max(0, v.totalSize() - LISTBOX_MAX_HEIGHT3);
7114
+ if (el.scrollTop > max) el.scrollTop = max;
7115
+ }
7116
+ });
7117
+ vue.onBeforeUnmount(() => vUnsub?.());
7118
+ vue.watch(
7119
+ () => props.items,
7120
+ () => {
7121
+ if (!props.virtual || !nav.value) return;
7122
+ nav.value.reset(props.items.length);
7123
+ activeIndex.value = nav.value.index;
7124
+ },
7125
+ { flush: "post" }
7126
+ );
7127
+ vue.watch(
7128
+ () => open.value,
7129
+ (isOpen) => {
7130
+ if (!isOpen) {
7131
+ v?.setScroll(0);
7132
+ return;
7133
+ }
7134
+ if (!props.virtual || !nav.value) return;
7135
+ const selIdx = props.items.findIndex(
7136
+ (it) => it.value === currentValue.value && !it.disabled
7137
+ );
7138
+ if (selIdx >= 0) nav.value.focus(selIdx);
7139
+ else nav.value.goFirst();
7140
+ activeIndex.value = nav.value.index;
7141
+ ensureVisible(activeIndex.value);
7142
+ focusOption(activeIndex.value);
7143
+ },
7144
+ { flush: "post" }
7145
+ );
7146
+ const ensureVisible = (index) => {
7147
+ if (!props.virtual || !v || index < 0 || index >= props.items.length) return;
7148
+ const el = listboxRef.value;
7149
+ if (!el) return;
7150
+ const top = el.scrollTop;
7151
+ const start = index * ROW_HEIGHT3;
7152
+ if (start >= top && start + ROW_HEIGHT3 <= top + LISTBOX_MAX_HEIGHT3) return;
7153
+ el.scrollTop = v.scrollToIndex(index, start < top ? "start" : "end");
7154
+ };
7155
+ const focusOption = (index) => {
7156
+ if (index < 0) return;
7157
+ vue.nextTick(() => {
7158
+ listboxRef.value?.querySelector(`[data-iris-list-index="${index}"]`)?.focus();
7159
+ });
7160
+ };
7161
+ const onListKeyDown = (event) => {
7162
+ if (!nav.value) return;
7163
+ const action = nav.value.handleKeyDown({
7164
+ key: event.key,
7165
+ preventDefault: () => event.preventDefault()
7166
+ });
7167
+ if (action.type === "focus" || action.type === "typeahead") {
7168
+ activeIndex.value = action.target;
7169
+ ensureVisible(action.target);
7170
+ focusOption(action.target);
7171
+ } else if (action.type === "select") {
7172
+ const item = props.items[action.target];
7173
+ if (item) onSelect(item);
7174
+ }
7175
+ };
6565
7176
  const sizeStyles = vue.computed(() => {
6566
7177
  const map = {
6567
- sm: { padding: "4px 24px 4px 8px", fontSize: "12px", minHeight: "28px" },
6568
- md: { padding: "6px 28px 6px 12px", fontSize: "14px", minHeight: "34px" },
6569
- lg: { padding: "8px 32px 8px 12px", fontSize: "16px", minHeight: "40px" }
7178
+ sm: {
7179
+ padding: "var(--iris-space-xxs, 4px) 24px var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)",
7180
+ fontSize: "var(--iris-font-size-xs, 12px)",
7181
+ minHeight: "28px"
7182
+ },
7183
+ md: {
7184
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-xl, 24px) var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
7185
+ fontSize: "var(--iris-font-size-md, 14px)",
7186
+ minHeight: "34px"
7187
+ },
7188
+ lg: {
7189
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-2xl, 32px) var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
7190
+ fontSize: "var(--iris-font-size-lg, 16px)",
7191
+ minHeight: "40px"
7192
+ }
6570
7193
  };
6571
7194
  return map[props.size];
6572
7195
  });
@@ -6614,12 +7237,144 @@ var IrisSelect = vue.defineComponent({
6614
7237
  })
6615
7238
  ]
6616
7239
  );
7240
+ const renderVirtualListbox = () => {
7241
+ const list = props.items;
7242
+ const optionStyle = (selected, active, disabled) => ({
7243
+ display: "flex",
7244
+ alignItems: "center",
7245
+ gap: "var(--iris-gap-sm)",
7246
+ padding: "var(--iris-padding-sm, 6px) var(--iris-padding-md)",
7247
+ borderRadius: "var(--iris-radius-sm)",
7248
+ cursor: disabled ? "not-allowed" : "pointer",
7249
+ opacity: disabled ? "0.5" : "1",
7250
+ fontSize: "var(--iris-font-size-md, 14px)",
7251
+ background: selected ? "var(--iris-surface-selected, rgba(99, 102, 241, 0.12))" : active ? "var(--iris-surface-hover)" : "transparent",
7252
+ color: "var(--iris-foreground)",
7253
+ fontWeight: selected ? "600" : "400",
7254
+ outline: "none"
7255
+ });
7256
+ const check = () => vue.h(
7257
+ "svg",
7258
+ {
7259
+ "aria-hidden": "true",
7260
+ width: "14",
7261
+ height: "14",
7262
+ viewBox: "0 0 24 24",
7263
+ fill: "none",
7264
+ stroke: "var(--iris-primary)",
7265
+ "stroke-width": "2.5",
7266
+ "stroke-linecap": "round",
7267
+ "stroke-linejoin": "round"
7268
+ },
7269
+ [vue.h("path", { d: "M20 6 9 17l-5-5" })]
7270
+ );
7271
+ return vue.h(
7272
+ "ul",
7273
+ {
7274
+ role: "listbox",
7275
+ "aria-label": t("select.options"),
7276
+ "data-iris-list": "",
7277
+ ref: (el) => {
7278
+ listboxRef.value = el ?? null;
7279
+ },
7280
+ onKeydown: onListKeyDown,
7281
+ onScroll: (e) => {
7282
+ v?.setScroll(e.currentTarget.scrollTop);
7283
+ },
7284
+ style: {
7285
+ listStyle: "none",
7286
+ margin: "0",
7287
+ padding: "var(--iris-padding-sm)",
7288
+ display: "flex",
7289
+ flexDirection: "column",
7290
+ outline: "none",
7291
+ maxHeight: "240px",
7292
+ overflowY: "auto"
7293
+ }
7294
+ },
7295
+ list.length === 0 ? [
7296
+ vue.h(
7297
+ "li",
7298
+ {
7299
+ role: "presentation",
7300
+ "data-iris-list-state": "empty",
7301
+ "aria-live": "polite",
7302
+ style: {
7303
+ listStyle: "none",
7304
+ padding: "12px",
7305
+ textAlign: "center",
7306
+ color: "var(--iris-muted)",
7307
+ fontSize: "var(--iris-font-size-md, 14px)"
7308
+ }
7309
+ },
7310
+ t("list.empty")
7311
+ )
7312
+ ] : [
7313
+ vue.h("li", {
7314
+ role: "presentation",
7315
+ "aria-hidden": "true",
7316
+ "data-iris-select-spacer": "",
7317
+ "data-iris-select-spacer-type": "top",
7318
+ style: { height: `${vstate.value.offsetBefore}px` }
7319
+ }),
7320
+ ...vstate.value.items.map((item) => {
7321
+ const opt = list[item.index];
7322
+ if (!opt) return null;
7323
+ const active = item.index === activeIndex.value;
7324
+ const selected = props.multiple ? selectedValues.value.includes(opt.value) : opt.value === currentValue.value;
7325
+ return vue.h(
7326
+ "li",
7327
+ {
7328
+ key: String(opt.value ?? item.index),
7329
+ role: "option",
7330
+ tabindex: active ? 0 : -1,
7331
+ "aria-selected": selected ? "true" : "false",
7332
+ "aria-disabled": opt.disabled ? "true" : void 0,
7333
+ "aria-setsize": list.length,
7334
+ "aria-posinset": item.index + 1,
7335
+ "data-iris-list-index": item.index,
7336
+ "data-iris-list-item": "",
7337
+ "data-state": selected ? "selected" : active ? "active" : "idle",
7338
+ onClick: opt.disabled ? void 0 : () => {
7339
+ activeIndex.value = item.index;
7340
+ onSelect(opt);
7341
+ },
7342
+ onFocus: () => {
7343
+ activeIndex.value = item.index;
7344
+ },
7345
+ onMouseenter: () => {
7346
+ activeIndex.value = item.index;
7347
+ },
7348
+ style: optionStyle(selected, active, !!opt.disabled)
7349
+ },
7350
+ [
7351
+ vue.h(
7352
+ "span",
7353
+ { style: { flex: "1", minWidth: "0" } },
7354
+ opt.label ?? String(opt.value)
7355
+ ),
7356
+ selected ? check() : null
7357
+ ]
7358
+ );
7359
+ }),
7360
+ vue.h("li", {
7361
+ role: "presentation",
7362
+ "aria-hidden": "true",
7363
+ "data-iris-select-spacer": "",
7364
+ "data-iris-select-spacer-type": "bottom",
7365
+ style: {
7366
+ height: `${vstate.value.totalSize - vstate.value.offsetBefore - vstate.value.items.length * ROW_HEIGHT3}px`
7367
+ }
7368
+ })
7369
+ ]
7370
+ );
7371
+ };
6617
7372
  return () => vue.h(
6618
7373
  IrisPopover,
6619
7374
  {
6620
7375
  open: open.value,
6621
7376
  placement: props.placement,
6622
- "onUpdate:open": (v) => open.value = v
7377
+ "onUpdate:open": (v2) => open.value = v2
6623
7378
  },
6624
7379
  {
6625
7380
  default: () => [
@@ -6660,7 +7415,7 @@ var IrisSelect = vue.defineComponent({
6660
7415
  style: { padding: "var(--iris-padding-sm)", minWidth: "180px" },
6661
7416
  ...props.teleport !== void 0 ? { teleport: props.teleport } : {}
6662
7417
  },
6663
- () => vue.h(IrisList, {
7418
+ () => props.virtual ? renderVirtualListbox() : vue.h(IrisList, {
6664
7419
  items: props.items,
6665
7420
  modelValue: currentValue.value,
6666
7421
  onSelect,
@@ -6765,7 +7520,7 @@ function badgeStyle(variant, tone, size) {
6765
7520
  fontWeight: "500",
6766
7521
  lineHeight: "1",
6767
7522
  whiteSpace: "nowrap",
6768
- fontSize: size === "sm" ? "11px" : "12px",
7523
+ fontSize: size === "sm" ? "var(--iris-font-size-xs, 12px)" : "12px",
6769
7524
  padding: size === "sm" ? "2px 6px" : "3px 8px"
6770
7525
  };
6771
7526
  switch (variant) {
@@ -6773,7 +7528,7 @@ function badgeStyle(variant, tone, size) {
6773
7528
  return {
6774
7529
  ...base,
6775
7530
  background: v,
6776
- color: "var(--iris-primary-foreground, #fff)",
7531
+ color: tone === "warning" ? "var(--iris-warning-foreground, #451a03)" : tone === "primary" ? "var(--iris-primary-foreground, #fff)" : tone === "success" ? "var(--iris-success-foreground, #0f172a)" : tone === "danger" ? "var(--iris-danger-foreground, #0f172a)" : "var(--iris-foreground, #0f172a)",
6777
7532
  border: "1px solid transparent"
6778
7533
  };
6779
7534
  case "outline":
@@ -6926,22 +7681,22 @@ var CSS4 = `
6926
7681
  }
6927
7682
  }
6928
7683
  `.trim();
6929
- var installed4 = false;
7684
+ var installed5 = false;
6930
7685
  function installSpinnerStyles() {
6931
- if (installed4) return;
7686
+ if (installed5) return;
6932
7687
  if (typeof document === "undefined") return;
6933
7688
  if (document.getElementById(__SPINNER_STYLE_ID)) {
6934
- installed4 = true;
7689
+ installed5 = true;
6935
7690
  return;
6936
7691
  }
6937
7692
  const el = document.createElement("style");
6938
7693
  el.id = __SPINNER_STYLE_ID;
6939
7694
  el.textContent = CSS4;
6940
7695
  document.head.appendChild(el);
6941
- installed4 = true;
7696
+ installed5 = true;
6942
7697
  }
6943
7698
  function __resetSpinnerStyles() {
6944
- installed4 = false;
7699
+ installed5 = false;
6945
7700
  if (typeof document !== "undefined") {
6946
7701
  document.getElementById(__SPINNER_STYLE_ID)?.remove();
6947
7702
  }
@@ -7016,7 +7771,7 @@ var IrisSpinner = vue.defineComponent({
7016
7771
  width: "1px",
7017
7772
  height: "1px",
7018
7773
  padding: "0",
7019
- margin: "-1px",
7774
+ margin: "calc(-1px)",
7020
7775
  overflow: "hidden",
7021
7776
  clip: "rect(0,0,0,0)",
7022
7777
  whiteSpace: "nowrap",
@@ -7073,22 +7828,22 @@ var CSS5 = `
7073
7828
  }
7074
7829
  }
7075
7830
  `.trim();
7076
- var installed5 = false;
7831
+ var installed6 = false;
7077
7832
  function installSkeletonStyles() {
7078
- if (installed5) return;
7833
+ if (installed6) return;
7079
7834
  if (typeof document === "undefined") return;
7080
7835
  if (document.getElementById(__SKELETON_STYLE_ID)) {
7081
- installed5 = true;
7836
+ installed6 = true;
7082
7837
  return;
7083
7838
  }
7084
7839
  const el = document.createElement("style");
7085
7840
  el.id = __SKELETON_STYLE_ID;
7086
7841
  el.textContent = CSS5;
7087
7842
  document.head.appendChild(el);
7088
- installed5 = true;
7843
+ installed6 = true;
7089
7844
  }
7090
7845
  function __resetSkeletonStyles() {
7091
- installed5 = false;
7846
+ installed6 = false;
7092
7847
  if (typeof document !== "undefined") {
7093
7848
  document.getElementById(__SKELETON_STYLE_ID)?.remove();
7094
7849
  }
@@ -7122,8 +7877,8 @@ var IrisSkeleton = vue.defineComponent({
7122
7877
  vue.onMounted(installSkeletonStyles);
7123
7878
  const style = vue.computed(() => {
7124
7879
  const w = props.width !== void 0 ? toCss(props.width) : defaultWidth(props.shape);
7125
- const h123 = props.height !== void 0 ? toCss(props.height) : defaultHeight(props.shape, props.width);
7126
- return { width: w, height: h123 };
7880
+ const h124 = props.height !== void 0 ? toCss(props.height) : defaultHeight(props.shape, props.width);
7881
+ return { width: w, height: h124 };
7127
7882
  });
7128
7883
  return () => vue.h("div", {
7129
7884
  ...attrs,
@@ -7173,22 +7928,22 @@ var CSS6 = `
7173
7928
  }
7174
7929
  }
7175
7930
  `.trim();
7176
- var installed6 = false;
7931
+ var installed7 = false;
7177
7932
  function installProgressStyles() {
7178
- if (installed6) return;
7933
+ if (installed7) return;
7179
7934
  if (typeof document === "undefined") return;
7180
7935
  if (document.getElementById(__PROGRESS_STYLE_ID)) {
7181
- installed6 = true;
7936
+ installed7 = true;
7182
7937
  return;
7183
7938
  }
7184
7939
  const el = document.createElement("style");
7185
7940
  el.id = __PROGRESS_STYLE_ID;
7186
7941
  el.textContent = CSS6;
7187
7942
  document.head.appendChild(el);
7188
- installed6 = true;
7943
+ installed7 = true;
7189
7944
  }
7190
7945
  function __resetProgressStyles() {
7191
- installed6 = false;
7946
+ installed7 = false;
7192
7947
  if (typeof document !== "undefined") {
7193
7948
  document.getElementById(__PROGRESS_STYLE_ID)?.remove();
7194
7949
  }
@@ -7254,7 +8009,7 @@ var IrisProgress = vue.defineComponent({
7254
8009
  }
7255
8010
  });
7256
8011
  var TONE_TO_VAR3 = {
7257
- info: "--iris-primary",
8012
+ info: "--iris-info",
7258
8013
  success: "--iris-success",
7259
8014
  warning: "--iris-warning",
7260
8015
  danger: "--iris-danger"
@@ -7346,7 +8101,7 @@ var IrisAlert = vue.defineComponent({
7346
8101
  border: "none",
7347
8102
  cursor: "pointer",
7348
8103
  color: "var(--iris-muted)",
7349
- fontSize: "16px",
8104
+ fontSize: "var(--iris-font-size-lg, 16px)",
7350
8105
  padding: "0",
7351
8106
  lineHeight: "1",
7352
8107
  flexShrink: "0"
@@ -7360,7 +8115,7 @@ var IrisAlert = vue.defineComponent({
7360
8115
  }
7361
8116
  });
7362
8117
  var TONE_TO_VAR4 = {
7363
- info: "--iris-primary",
8118
+ info: "--iris-info",
7364
8119
  success: "--iris-success",
7365
8120
  warning: "--iris-warning",
7366
8121
  danger: "--iris-danger",
@@ -7462,8 +8217,8 @@ var IrisBanner = vue.defineComponent({
7462
8217
  border: "none",
7463
8218
  cursor: "pointer",
7464
8219
  color: "var(--iris-muted)",
7465
- fontSize: "16px",
7466
- padding: "0 4px",
8220
+ fontSize: "var(--iris-font-size-lg, 16px)",
8221
+ padding: "0 var(--iris-space-xxs, 4px)",
7467
8222
  lineHeight: "1",
7468
8223
  flexShrink: "0"
7469
8224
  }
@@ -7629,7 +8384,7 @@ var IrisSlider = vue.defineComponent({
7629
8384
  borderRadius: "50%",
7630
8385
  background: "var(--iris-background)",
7631
8386
  border: "2px solid var(--iris-primary)",
7632
- boxShadow: dragging.value ? "0 0 0 4px rgba(99, 102, 241, 0.18)" : "0 1px 2px rgba(0,0,0,.15)",
8387
+ boxShadow: dragging.value ? "0 0 0 4px color-mix(in srgb, var(--iris-primary) 18%, transparent)" : "0 1px 2px rgba(0,0,0,.15)",
7633
8388
  cursor: props.disabled ? "not-allowed" : "grab",
7634
8389
  transition: "box-shadow 120ms ease",
7635
8390
  touchAction: "none",
@@ -7824,11 +8579,27 @@ var IrisPagination = vue.defineComponent({
7824
8579
  );
7825
8580
  }
7826
8581
  });
8582
+ var STYLE_ID3 = "iris-card-styles";
8583
+ var installed8 = false;
8584
+ function installCardStyles() {
8585
+ if (installed8 || typeof document === "undefined") return;
8586
+ installed8 = true;
8587
+ if (document.getElementById(STYLE_ID3)) return;
8588
+ const style = document.createElement("style");
8589
+ style.id = STYLE_ID3;
8590
+ style.textContent = `
8591
+ [data-iris-card-hover="true"]:hover {
8592
+ transform: translateY(-2px);
8593
+ box-shadow: var(--iris-shadow-lg);
8594
+ }
8595
+ `;
8596
+ document.head.appendChild(style);
8597
+ }
7827
8598
  var PADDING_MAP = {
7828
8599
  none: "0",
7829
8600
  sm: "12px",
7830
- md: "var(--iris-padding-md, 16px)",
7831
- lg: "24px"
8601
+ md: "var(--iris-padding-lg, 20px)",
8602
+ lg: "var(--iris-space-xl, 24px)"
7832
8603
  };
7833
8604
  var IrisCard = vue.defineComponent({
7834
8605
  name: "IrisCard",
@@ -7839,6 +8610,7 @@ var IrisCard = vue.defineComponent({
7839
8610
  hover: { type: Boolean, default: false }
7840
8611
  },
7841
8612
  setup(props, { slots, attrs }) {
8613
+ installCardStyles();
7842
8614
  const containerStyle = vue.computed(() => {
7843
8615
  const base = {
7844
8616
  display: "flex",
@@ -7851,7 +8623,7 @@ var IrisCard = vue.defineComponent({
7851
8623
  };
7852
8624
  switch (props.variant) {
7853
8625
  case "elevated":
7854
- return { ...base, boxShadow: "0 1px 2px rgba(0,0,0,.05), 0 4px 12px rgba(0,0,0,.06)" };
8626
+ return { ...base, boxShadow: "var(--iris-shadow-md)" };
7855
8627
  case "outline":
7856
8628
  return { ...base, border: "1px solid var(--iris-border)" };
7857
8629
  case "subtle":
@@ -7976,9 +8748,9 @@ var IrisDivider = vue.defineComponent({
7976
8748
  gap: "8px",
7977
8749
  margin: `${SPACING_MAP[props.spacing]} 0`,
7978
8750
  color: "var(--iris-muted)",
7979
- fontSize: "12px",
8751
+ fontSize: "var(--iris-font-size-xs, 12px)",
7980
8752
  textTransform: "uppercase",
7981
- letterSpacing: "0.04em",
8753
+ letterSpacing: "var(--iris-letter-spacing-wide, 0.04em)",
7982
8754
  ...attrs.style ?? {}
7983
8755
  }
7984
8756
  },
@@ -8034,7 +8806,19 @@ var IrisEmptyState = vue.defineComponent({
8034
8806
  "div",
8035
8807
  {
8036
8808
  "data-iris-empty-state-icon": "",
8037
- style: { color: "var(--iris-muted)", fontSize: "32px", lineHeight: "1" }
8809
+ style: {
8810
+ display: "flex",
8811
+ alignItems: "center",
8812
+ justifyContent: "center",
8813
+ width: "48px",
8814
+ height: "48px",
8815
+ borderRadius: "var(--iris-radius-lg, 12px)",
8816
+ background: "var(--iris-surface)",
8817
+ color: "var(--iris-muted)",
8818
+ opacity: "0.9",
8819
+ fontSize: "var(--iris-font-size-2xl, 20px)",
8820
+ lineHeight: "1"
8821
+ }
8038
8822
  },
8039
8823
  icon
8040
8824
  ) : null,
@@ -8042,7 +8826,7 @@ var IrisEmptyState = vue.defineComponent({
8042
8826
  "div",
8043
8827
  {
8044
8828
  "data-iris-empty-state-title": "",
8045
- style: { fontWeight: "600", fontSize: "16px" }
8829
+ style: { fontWeight: "600", fontSize: "var(--iris-font-size-lg, 16px)" }
8046
8830
  },
8047
8831
  titleNode
8048
8832
  ) : null,
@@ -8050,7 +8834,11 @@ var IrisEmptyState = vue.defineComponent({
8050
8834
  "div",
8051
8835
  {
8052
8836
  "data-iris-empty-state-description": "",
8053
- style: { color: "var(--iris-muted)", fontSize: "14px", maxWidth: "380px" }
8837
+ style: {
8838
+ color: "var(--iris-muted)",
8839
+ fontSize: "var(--iris-font-size-md, 14px)",
8840
+ maxWidth: "380px"
8841
+ }
8054
8842
  },
8055
8843
  descNode
8056
8844
  ) : null,
@@ -8072,7 +8860,7 @@ function chipStyle(variant, tone, size, clickable, disabled) {
8072
8860
  const base = {
8073
8861
  display: "inline-flex",
8074
8862
  alignItems: "center",
8075
- gap: "6px",
8863
+ gap: "var(--iris-space-xs, 8px)",
8076
8864
  borderRadius: "9999px",
8077
8865
  fontFamily: "var(--iris-font-family, inherit)",
8078
8866
  fontWeight: "500",
@@ -8173,8 +8961,8 @@ var IrisChip = vue.defineComponent({
8173
8961
  cursor: props.disabled ? "not-allowed" : "pointer",
8174
8962
  color: "inherit",
8175
8963
  padding: "0",
8176
- marginInlineStart: "2px",
8177
- fontSize: "12px",
8964
+ marginInlineStart: "var(--iris-space-xxs, 4px)",
8965
+ fontSize: "var(--iris-font-size-xs, 12px)",
8178
8966
  lineHeight: "1",
8179
8967
  flexShrink: "0",
8180
8968
  opacity: "0.7"
@@ -8563,7 +9351,7 @@ var IrisStepperStep = vue.defineComponent({
8563
9351
  background: s === "completed" ? color : "var(--iris-background)",
8564
9352
  color: s === "completed" ? "var(--iris-primary-foreground, #fff)" : color,
8565
9353
  border: `2px solid ${color}`,
8566
- fontSize: "13px",
9354
+ fontSize: "var(--iris-font-size-sm, 13px)",
8567
9355
  fontWeight: "600",
8568
9356
  lineHeight: "1",
8569
9357
  transition: "background-color 120ms ease, color 120ms ease, border-color 120ms ease"
@@ -8576,7 +9364,7 @@ var IrisStepperStep = vue.defineComponent({
8576
9364
  {
8577
9365
  "data-iris-stepper-title": "",
8578
9366
  style: {
8579
- fontSize: "13px",
9367
+ fontSize: "var(--iris-font-size-sm, 13px)",
8580
9368
  fontWeight: s === "active" ? "600" : "500",
8581
9369
  color: s === "pending" ? "var(--iris-muted)" : "var(--iris-foreground)"
8582
9370
  }
@@ -8587,7 +9375,7 @@ var IrisStepperStep = vue.defineComponent({
8587
9375
  "div",
8588
9376
  {
8589
9377
  "data-iris-stepper-description": "",
8590
- style: { fontSize: "12px", color: "var(--iris-muted)" }
9378
+ style: { fontSize: "var(--iris-font-size-xs, 12px)", color: "var(--iris-muted)" }
8591
9379
  },
8592
9380
  slots.description?.() ?? props.description
8593
9381
  ) : null;
@@ -8772,7 +9560,7 @@ var IrisToggleGroupItem = vue.defineComponent({
8772
9560
  display: "inline-flex",
8773
9561
  alignItems: "center",
8774
9562
  justifyContent: "center",
8775
- gap: "6px",
9563
+ gap: "var(--iris-space-xs, 8px)",
8776
9564
  padding: SIZE_PADDING[size],
8777
9565
  fontSize: SIZE_FONT[size],
8778
9566
  fontFamily: "inherit",
@@ -8894,8 +9682,14 @@ var IrisToggleGroup = vue.defineComponent({
8894
9682
  }
8895
9683
  });
8896
9684
  var SIZE_MAP6 = {
8897
- sm: { fontSize: "10px", padding: "2px 5px" },
8898
- md: { fontSize: "12px", padding: "3px 6px" }
9685
+ sm: {
9686
+ fontSize: "var(--iris-font-size-xs, 12px)",
9687
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)"
9688
+ },
9689
+ md: {
9690
+ fontSize: "var(--iris-font-size-xs, 12px)",
9691
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)"
9692
+ }
8899
9693
  };
8900
9694
  var IrisKbd = vue.defineComponent({
8901
9695
  name: "IrisKbd",
@@ -8916,7 +9710,7 @@ var IrisKbd = vue.defineComponent({
8916
9710
  const baseStyle = {
8917
9711
  display: "inline-flex",
8918
9712
  alignItems: "center",
8919
- gap: "3px",
9713
+ gap: "var(--iris-space-xxs, 4px)",
8920
9714
  fontFamily: "var(--iris-font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace)",
8921
9715
  fontSize: SIZE_MAP6[props.size].fontSize,
8922
9716
  verticalAlign: "middle"
@@ -9695,11 +10489,16 @@ var IrisTable = vue.defineComponent({
9695
10489
  },
9696
10490
  data: {
9697
10491
  type: Array,
9698
- required: true
10492
+ required: false,
10493
+ default: void 0
9699
10494
  },
9700
10495
  rowKey: { type: String, default: "id" },
9701
10496
  ...tableControlProps,
9702
10497
  striped: { type: Boolean, default: false },
10498
+ editConfig: {
10499
+ type: Object,
10500
+ default: void 0
10501
+ },
9703
10502
  bordered: { type: Boolean, default: true },
9704
10503
  /** Enable per-column resize handles. Combine with `v-model:columnWidths` for persistence. */
9705
10504
  resizableColumns: { type: Boolean, default: false },
@@ -9717,6 +10516,7 @@ var IrisTable = vue.defineComponent({
9717
10516
  loading: { type: Boolean, default: false },
9718
10517
  /** Show the error state instead of rows (takes precedence over loading). */
9719
10518
  error: { type: Boolean, default: false },
10519
+ onRetry: { type: Function, default: void 0 },
9720
10520
  /** Render only the horizontally-visible columns (+ pinned + overscan) for wide tables. */
9721
10521
  columnVirtualization: { type: Boolean, default: false },
9722
10522
  /**
@@ -9784,6 +10584,19 @@ var IrisTable = vue.defineComponent({
9784
10584
  expandedRowsChange: (_keys) => true
9785
10585
  },
9786
10586
  setup(props, { slots, attrs, emit }) {
10587
+ if (typeof document !== "undefined" && !document.getElementById("iris-table-row-styles")) {
10588
+ const style = document.createElement("style");
10589
+ style.id = "iris-table-row-styles";
10590
+ style.textContent = `
10591
+ [data-iris-table] [role="row"]:hover {
10592
+ --iris-cell-bg: var(--iris-surface-hover);
10593
+ }
10594
+ [data-iris-table-row-selected="true"] {
10595
+ --iris-cell-bg: var(--iris-surface-selected);
10596
+ }
10597
+ `;
10598
+ document.head.appendChild(style);
10599
+ }
9787
10600
  const { t } = useI18n();
9788
10601
  const grouped = vue.computed(() => props.columns.some((c) => c.children && c.children.length > 0));
9789
10602
  const leafColumns = vue.computed(
@@ -9968,6 +10781,16 @@ var IrisTable = vue.defineComponent({
9968
10781
  const oldValue = getCellValue2(row, column);
9969
10782
  const draft = editingDraft.value;
9970
10783
  const newValue = column.editor === "number" ? draft === "" || Number.isNaN(Number(draft)) ? oldValue : Number(draft) : draft;
10784
+ if (column.editRules && column.editRules.length > 0) {
10785
+ core.validateEditRulesAsync(column.editRules, draft, row).then((r) => {
10786
+ if (!r.valid) {
10787
+ editError.value = r.messages[0] ?? null;
10788
+ return;
10789
+ }
10790
+ finishCommit(row, column, rowIndex, oldValue, newValue);
10791
+ });
10792
+ return;
10793
+ }
9971
10794
  if (column.validate) {
9972
10795
  const error = column.validate(newValue, row);
9973
10796
  if (error) {
@@ -9975,6 +10798,14 @@ var IrisTable = vue.defineComponent({
9975
10798
  return;
9976
10799
  }
9977
10800
  }
10801
+ finishCommit(row, column, rowIndex, oldValue, newValue);
10802
+ editError.value = null;
10803
+ editingCellId.value = null;
10804
+ if (newValue !== oldValue) {
10805
+ emit("cellEdit", { row, column, oldValue, newValue, rowIndex });
10806
+ }
10807
+ };
10808
+ const finishCommit = (row, column, rowIndex, oldValue, newValue) => {
9978
10809
  editError.value = null;
9979
10810
  editingCellId.value = null;
9980
10811
  if (newValue !== oldValue) {
@@ -10021,9 +10852,9 @@ var IrisTable = vue.defineComponent({
10021
10852
  style: {
10022
10853
  display: "inline-flex",
10023
10854
  flexDirection: "column",
10024
- marginInlineStart: "4px",
10855
+ marginInlineStart: "var(--iris-space-xxs, 4px)",
10025
10856
  lineHeight: "0.6",
10026
- fontSize: "8px",
10857
+ fontSize: "var(--iris-font-size-xs, 12px)",
10027
10858
  color
10028
10859
  }
10029
10860
  },
@@ -10234,18 +11065,33 @@ var IrisTable = vue.defineComponent({
10234
11065
  display: "flex",
10235
11066
  alignItems: "center",
10236
11067
  justifyContent: "center",
10237
- padding: "8px",
11068
+ padding: "8px 12px",
10238
11069
  background: "var(--iris-surface)",
10239
11070
  borderBottom: "1px solid var(--iris-border)"
10240
11071
  }
10241
11072
  },
10242
11073
  props.selectable === "multi" ? [
10243
- vue.h(IrisCheckbox, {
10244
- modelValue: allSelected.value ? true : someSelected.value ? "indeterminate" : false,
10245
- size: "sm",
10246
- ariaLabel: t("table.selectAll"),
10247
- "onUpdate:modelValue": toggleAll
10248
- })
11074
+ [
11075
+ vue.h(IrisCheckbox, {
11076
+ modelValue: allSelected.value ? true : someSelected.value ? "indeterminate" : false,
11077
+ size: "sm",
11078
+ ariaLabel: t("table.selectAll"),
11079
+ "onUpdate:modelValue": toggleAll
11080
+ }),
11081
+ props.selection && props.selection.length > 0 ? vue.h(
11082
+ "span",
11083
+ {
11084
+ "data-iris-table-selected-count": "",
11085
+ style: {
11086
+ marginInlineStart: "var(--iris-space-xs, 8px)",
11087
+ fontSize: "var(--iris-font-size-sm, 13px)",
11088
+ color: "var(--iris-muted)",
11089
+ whiteSpace: "nowrap"
11090
+ }
11091
+ },
11092
+ t("table.selectedCount", { count: String(props.selection.length) })
11093
+ ) : null
11094
+ ]
10249
11095
  ] : ""
10250
11096
  )
10251
11097
  );
@@ -10281,7 +11127,7 @@ var IrisTable = vue.defineComponent({
10281
11127
  background: "var(--iris-surface)",
10282
11128
  borderBottom: "1px solid var(--iris-border)",
10283
11129
  fontWeight: "600",
10284
- fontSize: "13px",
11130
+ fontSize: "var(--iris-font-size-md, 14px)",
10285
11131
  color: "var(--iris-foreground)",
10286
11132
  whiteSpace: "nowrap",
10287
11133
  overflow: "hidden",
@@ -10338,7 +11184,7 @@ var IrisTable = vue.defineComponent({
10338
11184
  display: "flex",
10339
11185
  alignItems: "center",
10340
11186
  justifyContent: "center",
10341
- padding: "8px",
11187
+ padding: "8px 12px",
10342
11188
  background: "var(--iris-surface)",
10343
11189
  borderBottom: "1px solid var(--iris-border)"
10344
11190
  }
@@ -10417,7 +11263,7 @@ var IrisTable = vue.defineComponent({
10417
11263
  background: "var(--iris-surface)",
10418
11264
  borderBottom: "1px solid var(--iris-border)",
10419
11265
  fontWeight: "600",
10420
- fontSize: "13px",
11266
+ fontSize: "var(--iris-font-size-md, 14px)",
10421
11267
  color: "var(--iris-foreground)",
10422
11268
  whiteSpace: "nowrap",
10423
11269
  overflow: "hidden",
@@ -10461,7 +11307,7 @@ var IrisTable = vue.defineComponent({
10461
11307
  display: "flex",
10462
11308
  alignItems: "center",
10463
11309
  justifyContent: "center",
10464
- padding: "8px",
11310
+ padding: "8px 12px",
10465
11311
  borderBottom: "1px solid var(--iris-border)"
10466
11312
  }
10467
11313
  },
@@ -10504,7 +11350,7 @@ var IrisTable = vue.defineComponent({
10504
11350
  display: "flex",
10505
11351
  alignItems: "center",
10506
11352
  justifyContent: "center",
10507
- padding: "8px",
11353
+ padding: "8px 12px",
10508
11354
  borderBottom: "1px solid var(--iris-border)"
10509
11355
  }
10510
11356
  },
@@ -10558,12 +11404,12 @@ var IrisTable = vue.defineComponent({
10558
11404
  width: "100%",
10559
11405
  border: `1px solid ${error ? "var(--iris-danger)" : "var(--iris-primary)"}`,
10560
11406
  borderRadius: "var(--iris-radius-sm)",
10561
- padding: "4px 6px",
11407
+ padding: "var(--iris-space-xxs, 4px) var(--iris-padding-sm, 6px)",
10562
11408
  font: "inherit",
10563
11409
  background: "var(--iris-background)",
10564
11410
  color: "var(--iris-foreground)",
10565
11411
  outline: "none",
10566
- boxShadow: "0 0 0 3px rgba(99, 102, 241, 0.18)"
11412
+ boxShadow: "0 0 0 3px color-mix(in srgb, var(--iris-primary) 18%, transparent)"
10567
11413
  }
10568
11414
  });
10569
11415
  content = error ? [
@@ -10575,8 +11421,8 @@ var IrisTable = vue.defineComponent({
10575
11421
  role: "alert",
10576
11422
  "data-iris-table-editor-error": "",
10577
11423
  style: {
10578
- marginTop: "2px",
10579
- fontSize: "12px",
11424
+ marginTop: "var(--iris-space-xxs, 4px)",
11425
+ fontSize: "var(--iris-font-size-xs, 12px)",
10580
11426
  color: "var(--iris-danger)"
10581
11427
  }
10582
11428
  },
@@ -10667,13 +11513,15 @@ var IrisTable = vue.defineComponent({
10667
11513
  }
10668
11514
  } : {},
10669
11515
  onDblclick: col.editable ? () => beginEdit(row, col, id) : void 0,
11516
+ onClick: col.editable && props.editConfig?.trigger === "click" ? () => beginEdit(row, col, id) : void 0,
10670
11517
  style: {
10671
11518
  display: "flex",
10672
11519
  alignItems: "center",
10673
11520
  justifyContent: align === "right" ? "flex-end" : align === "center" ? "center" : "flex-start",
10674
- padding: isEditing ? "4px" : "8px var(--iris-padding-md)",
11521
+ background: "var(--iris-cell-bg, transparent)",
11522
+ padding: isEditing ? "var(--iris-space-xxs, 4px)" : "var(--iris-space-xs, 8px) var(--iris-padding-md)",
10675
11523
  borderBottom: "1px solid var(--iris-border)",
10676
- fontSize: "14px",
11524
+ fontSize: "var(--iris-font-size-md, 14px)",
10677
11525
  whiteSpace: "nowrap",
10678
11526
  overflow: "hidden",
10679
11527
  textOverflow: "ellipsis",
@@ -10707,7 +11555,7 @@ var IrisTable = vue.defineComponent({
10707
11555
  style: {
10708
11556
  display: "grid",
10709
11557
  gridTemplateColumns: gridTemplate.value,
10710
- background: selected ? "var(--iris-surface-hover)" : props.striped && index % 2 === 1 ? "var(--iris-surface)" : "transparent",
11558
+ background: selected ? "var(--iris-surface-hover)" : "transparent",
10711
11559
  transition: "background-color 120ms ease",
10712
11560
  cursor: "default",
10713
11561
  ...style
@@ -10723,11 +11571,31 @@ var IrisTable = vue.defineComponent({
10723
11571
  };
10724
11572
  let bodyNode;
10725
11573
  if (props.error) {
10726
- bodyNode = vue.h(
10727
- "div",
10728
- { role: "row", "data-iris-table-row": "error", style: stateRowStyle },
10729
- slots.error ? slots.error() : t("table.error")
10730
- );
11574
+ bodyNode = vue.h("div", { role: "row", "data-iris-table-row": "error", style: stateRowStyle }, [
11575
+ vue.h(
11576
+ "span",
11577
+ { style: { marginInlineEnd: props.onRetry ? "var(--iris-space-sm, 12px)" : "0px" } },
11578
+ slots.error ? slots.error() : t("table.error")
11579
+ ),
11580
+ props.onRetry ? vue.h(
11581
+ "button",
11582
+ {
11583
+ type: "button",
11584
+ "data-iris-table-retry": "",
11585
+ onClick: props.onRetry,
11586
+ style: {
11587
+ border: "1px solid var(--iris-border)",
11588
+ background: "var(--iris-surface)",
11589
+ color: "var(--iris-foreground)",
11590
+ borderRadius: "var(--iris-radius-sm, 4px)",
11591
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)",
11592
+ fontSize: "var(--iris-font-size-sm, 13px)",
11593
+ cursor: "pointer"
11594
+ }
11595
+ },
11596
+ t("table.retry")
11597
+ ) : null
11598
+ ]);
10731
11599
  } else if (props.loading) {
10732
11600
  bodyNode = vue.h(
10733
11601
  "div",
@@ -10821,7 +11689,7 @@ var IrisTable = vue.defineComponent({
10821
11689
  display: "flex",
10822
11690
  alignItems: "center",
10823
11691
  justifyContent: "center",
10824
- padding: "8px"
11692
+ padding: "8px 12px"
10825
11693
  }
10826
11694
  })
10827
11695
  );
@@ -10846,8 +11714,8 @@ var IrisTable = vue.defineComponent({
10846
11714
  display: "flex",
10847
11715
  alignItems: "center",
10848
11716
  justifyContent: align === "right" ? "flex-end" : align === "center" ? "center" : "flex-start",
10849
- padding: "8px var(--iris-padding-md)",
10850
- fontSize: "14px",
11717
+ padding: "var(--iris-space-xs, 8px) var(--iris-padding-md)",
11718
+ fontSize: "var(--iris-font-size-md, 14px)",
10851
11719
  whiteSpace: "nowrap",
10852
11720
  overflow: "hidden",
10853
11721
  textOverflow: "ellipsis",
@@ -10898,6 +11766,7 @@ var IrisTable = vue.defineComponent({
10898
11766
  style: {
10899
11767
  background: "var(--iris-background)",
10900
11768
  color: "var(--iris-foreground)",
11769
+ fontSize: "var(--iris-font-size-md, 14px)",
10901
11770
  border: props.bordered ? "1px solid var(--iris-border)" : "none",
10902
11771
  borderRadius: "var(--iris-radius-md)",
10903
11772
  // Column virtualization turns the table into a horizontal scroll container.
@@ -11024,11 +11893,11 @@ var IrisTabs = vue.defineComponent({
11024
11893
  });
11025
11894
 
11026
11895
  // src/primitives/tabs/styles.ts
11027
- var STYLE_ID2 = "iris-tabs-styles";
11896
+ var STYLE_ID4 = "iris-tabs-styles";
11028
11897
  var CSS7 = `
11029
11898
  :where(.iris-tabs-list) {
11030
11899
  display: flex;
11031
- gap: 2px;
11900
+ gap: var(--iris-space-xxs, 4px);
11032
11901
  }
11033
11902
  :where(.iris-tabs-list[data-orientation="horizontal"]) {
11034
11903
  flex-direction: row;
@@ -11039,8 +11908,8 @@ var CSS7 = `
11039
11908
  border-inline-end: 1px solid var(--iris-border);
11040
11909
  }
11041
11910
  :where(.iris-tabs-trigger) {
11042
- padding: 8px var(--iris-padding-md);
11043
- font-size: 14px;
11911
+ padding: var(--iris-space-xs, 8px) var(--iris-padding-md);
11912
+ font-size: var(--iris-font-size-md, 14px);
11044
11913
  font-weight: 500;
11045
11914
  font-family: inherit;
11046
11915
  cursor: pointer;
@@ -11052,11 +11921,11 @@ var CSS7 = `
11052
11921
  transition: color 120ms ease, border-color 120ms ease;
11053
11922
  }
11054
11923
  :where(.iris-tabs-trigger[data-orientation="horizontal"]) {
11055
- margin-bottom: -1px;
11924
+ margin-bottom: calc(-1px);
11056
11925
  border-bottom: 2px solid transparent;
11057
11926
  }
11058
11927
  :where(.iris-tabs-trigger[data-orientation="vertical"]) {
11059
- margin-inline-end: -1px;
11928
+ margin-inline-end: calc(-1px);
11060
11929
  border-inline-end: 2px solid transparent;
11061
11930
  }
11062
11931
  :where(.iris-tabs-trigger[data-state="active"]) {
@@ -11077,19 +11946,19 @@ var CSS7 = `
11077
11946
  outline-offset: 2px;
11078
11947
  }
11079
11948
  `.trim();
11080
- var installed7 = false;
11949
+ var installed9 = false;
11081
11950
  function installTabsStyles() {
11082
- if (installed7) return;
11951
+ if (installed9) return;
11083
11952
  if (typeof document === "undefined") return;
11084
- if (document.getElementById(STYLE_ID2)) {
11085
- installed7 = true;
11953
+ if (document.getElementById(STYLE_ID4)) {
11954
+ installed9 = true;
11086
11955
  return;
11087
11956
  }
11088
11957
  const style = document.createElement("style");
11089
- style.id = STYLE_ID2;
11958
+ style.id = STYLE_ID4;
11090
11959
  style.textContent = CSS7;
11091
11960
  document.head.appendChild(style);
11092
- installed7 = true;
11961
+ installed9 = true;
11093
11962
  }
11094
11963
 
11095
11964
  // src/primitives/tabs/TabsList.ts
@@ -11290,6 +12159,7 @@ var IrisMenu = vue.defineComponent({
11290
12159
  triggerRef: vue.ref(null),
11291
12160
  contentRef: vue.ref(null),
11292
12161
  contentId: vue.useId(),
12162
+ treeId: vue.useId(),
11293
12163
  placement: props.placement,
11294
12164
  offset: props.offset,
11295
12165
  closeRoot
@@ -11377,6 +12247,10 @@ var IrisMenuContent = vue.defineComponent({
11377
12247
  useDismiss({
11378
12248
  enabled: ctx.open,
11379
12249
  exclude: [ctx.triggerRef, innerRef],
12250
+ // B1: nested submenu surfaces (teleported to body) belong to this menu
12251
+ // tree — pointerdown inside them must not dismiss the root. `Element`
12252
+ // (not `HTMLElement`) so SVG icons inside teleported items also match.
12253
+ excludePredicate: (target) => target instanceof Element ? !!target.closest(`[data-iris-menu-tree="${ctx.treeId}"]`) : false,
11380
12254
  onDismiss: () => ctx.setOpen(false)
11381
12255
  });
11382
12256
  vue.watch(ctx.open, async (open) => {
@@ -11439,12 +12313,12 @@ var IrisMenuContent = vue.defineComponent({
11439
12313
  onKeydown: onKeyDown,
11440
12314
  style: {
11441
12315
  ...floatingStyles.value,
11442
- background: "var(--iris-surface)",
12316
+ background: "var(--iris-surface-floating)",
11443
12317
  color: "var(--iris-foreground)",
11444
12318
  border: "1px solid var(--iris-border)",
11445
12319
  borderRadius: "var(--iris-radius-md)",
11446
12320
  padding: "var(--iris-padding-sm)",
11447
- boxShadow: "0 8px 24px -8px rgba(0, 0, 0, 0.16), 0 4px 8px -2px rgba(0, 0, 0, 0.08)",
12321
+ boxShadow: "var(--iris-shadow-lg)",
11448
12322
  minWidth: "180px",
11449
12323
  outline: "none",
11450
12324
  zIndex: "1000",
@@ -11507,14 +12381,14 @@ var IrisMenuItem = vue.defineComponent({
11507
12381
  display: "flex",
11508
12382
  alignItems: "center",
11509
12383
  gap: "var(--iris-gap-sm)",
11510
- padding: "6px var(--iris-padding-md)",
12384
+ padding: "var(--iris-padding-sm, 6px) var(--iris-padding-md)",
11511
12385
  borderRadius: "var(--iris-radius-sm)",
11512
12386
  cursor: props.disabled ? "not-allowed" : "pointer",
11513
12387
  opacity: props.disabled ? "0.5" : "1",
11514
12388
  background: hovered.value && !props.disabled ? "var(--iris-surface-hover)" : "transparent",
11515
12389
  color: "inherit",
11516
12390
  outline: "none",
11517
- fontSize: "14px",
12391
+ fontSize: "var(--iris-font-size-md, 14px)",
11518
12392
  transition: "background-color 80ms ease",
11519
12393
  ...attrs.style ?? {}
11520
12394
  }
@@ -11540,7 +12414,8 @@ var IrisMenuSeparator = vue.defineComponent({
11540
12414
  });
11541
12415
  }
11542
12416
  });
11543
- var HOVER_OPEN_DELAY = 100;
12417
+ var HOVER_OPEN_DELAY2 = 100;
12418
+ var HOVER_CLOSE_DELAY2 = 150;
11544
12419
  var IrisMenuSub = vue.defineComponent({
11545
12420
  name: "IrisMenuSub",
11546
12421
  inheritAttrs: false,
@@ -11560,20 +12435,42 @@ var IrisMenuSub = vue.defineComponent({
11560
12435
  const triggerRef = vue.ref(null);
11561
12436
  const contentRef = vue.ref(null);
11562
12437
  let openTimer = null;
12438
+ let closeTimer = null;
12439
+ let focusOnOpen = false;
12440
+ let keyboardManaged = false;
11563
12441
  const clearTimer = () => {
11564
12442
  if (openTimer) {
11565
12443
  clearTimeout(openTimer);
11566
12444
  openTimer = null;
11567
12445
  }
11568
12446
  };
12447
+ const clearCloseTimer = () => {
12448
+ if (closeTimer) {
12449
+ clearTimeout(closeTimer);
12450
+ closeTimer = null;
12451
+ }
12452
+ };
11569
12453
  const scheduleOpen = () => {
11570
12454
  clearTimer();
12455
+ clearCloseTimer();
11571
12456
  openTimer = setTimeout(() => {
11572
12457
  open.value = true;
11573
12458
  openTimer = null;
11574
- }, HOVER_OPEN_DELAY);
12459
+ }, HOVER_OPEN_DELAY2);
12460
+ };
12461
+ const scheduleClose = () => {
12462
+ clearTimer();
12463
+ clearCloseTimer();
12464
+ closeTimer = setTimeout(() => {
12465
+ closeTimer = null;
12466
+ keyboardManaged = false;
12467
+ open.value = false;
12468
+ }, HOVER_CLOSE_DELAY2);
11575
12469
  };
11576
- vue.onScopeDispose(clearTimer);
12470
+ vue.onScopeDispose(() => {
12471
+ clearTimer();
12472
+ clearCloseTimer();
12473
+ });
11577
12474
  const { floatingStyles } = useFloating({
11578
12475
  anchor: triggerRef,
11579
12476
  floating: contentRef,
@@ -11587,32 +12484,50 @@ var IrisMenuSub = vue.defineComponent({
11587
12484
  triggerRef,
11588
12485
  contentRef,
11589
12486
  contentId: "",
12487
+ treeId: parentCtx.treeId,
11590
12488
  placement: "right-start",
11591
12489
  offset: 0,
11592
12490
  closeRoot: parentCtx.closeRoot
11593
12491
  });
11594
12492
  const onTriggerPointerEnter = () => scheduleOpen();
11595
- const onTriggerPointerLeave = () => clearTimer();
12493
+ const onTriggerPointerLeave = () => scheduleClose();
11596
12494
  const onTriggerClick = (event) => {
11597
12495
  if (event.defaultPrevented) return;
11598
12496
  clearTimer();
12497
+ clearCloseTimer();
12498
+ keyboardManaged = false;
11599
12499
  open.value = !open.value;
11600
12500
  };
11601
12501
  const onTriggerKeyDown = (event) => {
11602
- if (event.key === "ArrowRight" || event.key === "Enter" || event.key === " ") {
12502
+ if (event.key === "ArrowRight" || event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
11603
12503
  event.preventDefault();
11604
- open.value = true;
12504
+ event.stopPropagation();
12505
+ focusOnOpen = true;
12506
+ keyboardManaged = true;
12507
+ if (open.value) {
12508
+ focusOnOpen = false;
12509
+ focusFirstItem();
12510
+ } else {
12511
+ open.value = true;
12512
+ }
11605
12513
  } else if (event.key === "ArrowLeft") {
11606
12514
  event.preventDefault();
12515
+ event.stopPropagation();
11607
12516
  open.value = false;
11608
12517
  }
11609
12518
  };
11610
- vue.watch(open, async (next) => {
12519
+ const focusFirstItem = () => {
12520
+ void vue.nextTick(() => {
12521
+ contentRef.value?.querySelector('[role="menuitem"]')?.focus();
12522
+ });
12523
+ };
12524
+ vue.watch(open, (next) => {
11611
12525
  if (next) {
11612
- await new Promise((r) => setTimeout(r, 0));
11613
- const first = contentRef.value?.querySelector('[role="menuitem"]');
11614
- first?.focus();
11615
- } else {
12526
+ if (!focusOnOpen) return;
12527
+ focusOnOpen = false;
12528
+ focusFirstItem();
12529
+ } else if (keyboardManaged) {
12530
+ keyboardManaged = false;
11616
12531
  triggerRef.value?.focus?.();
11617
12532
  }
11618
12533
  });
@@ -11658,11 +12573,11 @@ var IrisMenuSub = vue.defineComponent({
11658
12573
  alignItems: "center",
11659
12574
  justifyContent: "space-between",
11660
12575
  gap: "var(--iris-gap-sm)",
11661
- padding: "6px var(--iris-padding-md)",
12576
+ padding: "var(--iris-padding-sm, 6px) var(--iris-padding-md)",
11662
12577
  borderRadius: "var(--iris-radius-sm)",
11663
12578
  cursor: "pointer",
11664
12579
  outline: "none",
11665
- fontSize: "14px",
12580
+ fontSize: "var(--iris-font-size-md, 14px)",
11666
12581
  background: open.value ? "var(--iris-surface-hover)" : "transparent"
11667
12582
  }
11668
12583
  },
@@ -11690,17 +12605,24 @@ var IrisMenuSub = vue.defineComponent({
11690
12605
  role: "menu",
11691
12606
  tabindex: -1,
11692
12607
  "data-iris-menu-sub": "",
12608
+ // B1: tag the surface with the root menu's tree id so the root
12609
+ // dismiss ignores pointerdown inside teleported submenus.
12610
+ "data-iris-menu-tree": parentCtx.treeId,
11693
12611
  "data-state": "open",
11694
12612
  onKeydown: onContentKeyDown,
11695
- onPointerenter: clearTimer,
12613
+ onPointerenter: () => {
12614
+ clearTimer();
12615
+ clearCloseTimer();
12616
+ },
12617
+ onPointerleave: scheduleClose,
11696
12618
  style: {
11697
12619
  ...floatingStyles.value,
11698
- background: "var(--iris-surface)",
12620
+ background: "var(--iris-surface-floating)",
11699
12621
  color: "var(--iris-foreground)",
11700
12622
  border: "1px solid var(--iris-border)",
11701
12623
  borderRadius: "var(--iris-radius-md)",
11702
12624
  padding: "var(--iris-padding-sm)",
11703
- boxShadow: "0 8px 24px -8px rgba(0, 0, 0, 0.16), 0 4px 8px -2px rgba(0, 0, 0, 0.08)",
12625
+ boxShadow: "var(--iris-shadow-lg)",
11704
12626
  minWidth: "180px",
11705
12627
  outline: "none",
11706
12628
  zIndex: "1001"
@@ -11763,14 +12685,14 @@ var VARIANT_BORDER = {
11763
12685
  success: "var(--iris-success)",
11764
12686
  danger: "var(--iris-danger)",
11765
12687
  warning: "var(--iris-warning)",
11766
- info: "var(--iris-primary)"
12688
+ info: "var(--iris-info)"
11767
12689
  };
11768
12690
  var VARIANT_ACCENT = {
11769
12691
  default: "var(--iris-muted)",
11770
12692
  success: "var(--iris-success)",
11771
12693
  danger: "var(--iris-danger)",
11772
12694
  warning: "var(--iris-warning)",
11773
- info: "var(--iris-primary)"
12695
+ info: "var(--iris-info)"
11774
12696
  };
11775
12697
  var SWIPE_DISMISS_PX = 80;
11776
12698
  var IrisToastViewport = vue.defineComponent({
@@ -11791,6 +12713,7 @@ var IrisToastViewport = vue.defineComponent({
11791
12713
  max: { type: Number, default: 5 }
11792
12714
  },
11793
12715
  setup(props, { attrs }) {
12716
+ installFloatingAnimations();
11794
12717
  const { t } = useI18n();
11795
12718
  const toasts = vue.ref(getToasts());
11796
12719
  const hovered = vue.ref(false);
@@ -11943,15 +12866,16 @@ var IrisToastViewport = vue.defineComponent({
11943
12866
  display: "flex",
11944
12867
  alignItems: "flex-start",
11945
12868
  gap: "var(--iris-gap-md)",
11946
- background: "var(--iris-surface)",
12869
+ background: "var(--iris-surface-floating)",
12870
+ animation: ANIM_TOAST,
11947
12871
  color: "var(--iris-foreground)",
11948
12872
  border: `1px solid ${VARIANT_BORDER[toast.variant]}`,
11949
12873
  borderInlineStart: `4px solid ${VARIANT_ACCENT[toast.variant]}`,
11950
12874
  borderRadius: "var(--iris-radius-md)",
11951
12875
  padding: "var(--iris-padding-md)",
11952
- boxShadow: "0 8px 24px -8px rgba(0, 0, 0, 0.16), 0 4px 8px -2px rgba(0, 0, 0, 0.08)",
12876
+ boxShadow: "var(--iris-shadow-lg)",
11953
12877
  minWidth: "280px",
11954
- fontSize: "14px"
12878
+ fontSize: "var(--iris-font-size-md, 14px)"
11955
12879
  }
11956
12880
  },
11957
12881
  [
@@ -11959,7 +12883,13 @@ var IrisToastViewport = vue.defineComponent({
11959
12883
  toast.title && vue.h("div", { style: { fontWeight: "600" } }, toast.title),
11960
12884
  toast.description && vue.h(
11961
12885
  "div",
11962
- { style: { color: "var(--iris-muted)", fontSize: "13px", marginTop: "2px" } },
12886
+ {
12887
+ style: {
12888
+ color: "var(--iris-muted)",
12889
+ fontSize: "var(--iris-font-size-sm, 13px)",
12890
+ marginTop: "var(--iris-space-xxs, 4px)"
12891
+ }
12892
+ },
11963
12893
  toast.description
11964
12894
  )
11965
12895
  ]),
@@ -11977,8 +12907,8 @@ var IrisToastViewport = vue.defineComponent({
11977
12907
  color: VARIANT_ACCENT[toast.variant],
11978
12908
  fontWeight: "600",
11979
12909
  cursor: "pointer",
11980
- padding: "4px 8px",
11981
- fontSize: "13px",
12910
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)",
12911
+ fontSize: "var(--iris-font-size-sm, 13px)",
11982
12912
  fontFamily: "inherit"
11983
12913
  }
11984
12914
  },
@@ -11994,11 +12924,11 @@ var IrisToastViewport = vue.defineComponent({
11994
12924
  background: "transparent",
11995
12925
  border: "none",
11996
12926
  cursor: "pointer",
11997
- padding: "4px",
12927
+ padding: "var(--iris-space-xxs, 4px)",
11998
12928
  color: "var(--iris-muted)",
11999
12929
  lineHeight: "1",
12000
12930
  fontFamily: "inherit",
12001
- fontSize: "16px"
12931
+ fontSize: "var(--iris-font-size-lg, 16px)"
12002
12932
  }
12003
12933
  },
12004
12934
  "\xD7"
@@ -12029,10 +12959,10 @@ var IrisToastViewport = vue.defineComponent({
12029
12959
  }
12030
12960
  });
12031
12961
  var TREE_STATE_STYLE = {
12032
- padding: "12px",
12962
+ padding: "var(--iris-space-sm, 12px)",
12033
12963
  textAlign: "center",
12034
12964
  color: "var(--iris-muted)",
12035
- fontSize: "14px"
12965
+ fontSize: "var(--iris-font-size-md, 14px)"
12036
12966
  };
12037
12967
  var IrisTree = vue.defineComponent({
12038
12968
  name: "IrisTree",
@@ -12384,7 +13314,7 @@ var IrisTree = vue.defineComponent({
12384
13314
  background: isSelected ? "var(--iris-primary)" : isActive ? "var(--iris-surface-hover)" : "transparent",
12385
13315
  color: isSelected ? "var(--iris-primary-foreground)" : "var(--iris-foreground)",
12386
13316
  outline: "none",
12387
- fontSize: "14px"
13317
+ fontSize: "var(--iris-font-size-md, 14px)"
12388
13318
  }
12389
13319
  },
12390
13320
  [chevron, checkbox, vue.h("span", { style: { flex: "1", minWidth: "0" } }, node.label)]
@@ -12405,7 +13335,7 @@ var IrisTree = vue.defineComponent({
12405
13335
  style: {
12406
13336
  display: "flex",
12407
13337
  flexDirection: "column",
12408
- gap: "2px",
13338
+ gap: "var(--iris-space-xxs, 4px)",
12409
13339
  outline: "none",
12410
13340
  ...attrs.style ?? {}
12411
13341
  }
@@ -12416,7 +13346,7 @@ var IrisTree = vue.defineComponent({
12416
13346
  });
12417
13347
  var VARIANT_COLOR = {
12418
13348
  default: "var(--iris-primary)",
12419
- success: "var(--iris-success, #16a34a)",
13349
+ success: "var(--iris-success, #10b981)",
12420
13350
  warning: "var(--iris-warning, #f59e0b)",
12421
13351
  danger: "var(--iris-danger)",
12422
13352
  info: "var(--iris-info, #0ea5e9)"
@@ -12500,7 +13430,10 @@ var IrisTimeline = vue.defineComponent({
12500
13430
  "div",
12501
13431
  {
12502
13432
  "data-iris-timeline-time": "",
12503
- style: { fontSize: "12px", color: "var(--iris-muted)" }
13433
+ style: {
13434
+ fontSize: "var(--iris-font-size-xs, 12px)",
13435
+ color: "var(--iris-muted)"
13436
+ }
12504
13437
  },
12505
13438
  item.time
12506
13439
  ) : null,
@@ -12516,7 +13449,10 @@ var IrisTimeline = vue.defineComponent({
12516
13449
  "div",
12517
13450
  {
12518
13451
  "data-iris-timeline-desc": "",
12519
- style: { fontSize: "14px", color: "var(--iris-foreground)" }
13452
+ style: {
13453
+ fontSize: "var(--iris-font-size-md, 14px)",
13454
+ color: "var(--iris-foreground)"
13455
+ }
12520
13456
  },
12521
13457
  item.description
12522
13458
  ) : null
@@ -12543,7 +13479,7 @@ var ARROW_BTN = {
12543
13479
  background: "var(--iris-background)",
12544
13480
  color: "var(--iris-foreground)",
12545
13481
  cursor: "pointer",
12546
- fontSize: "18px",
13482
+ fontSize: "var(--iris-font-size-xl, 18px)",
12547
13483
  lineHeight: "1"
12548
13484
  };
12549
13485
  function prefersReducedMotion() {
@@ -12725,7 +13661,7 @@ var IrisCarousel = vue.defineComponent({
12725
13661
  style: {
12726
13662
  display: "flex",
12727
13663
  justifyContent: "center",
12728
- gap: "6px",
13664
+ gap: "var(--iris-space-xs, 8px)",
12729
13665
  marginBlockStart: "8px"
12730
13666
  }
12731
13667
  },
@@ -12771,13 +13707,22 @@ var IrisCarousel = vue.defineComponent({
12771
13707
  };
12772
13708
  }
12773
13709
  });
12774
- var VALUE_FONT = { sm: 20, md: 28, lg: 36 };
13710
+ var VALUE_FONT = {
13711
+ sm: "var(--iris-font-size-2xl, 20px)",
13712
+ md: "var(--iris-font-size-3xl, 24px)",
13713
+ lg: "var(--iris-font-size-4xl, 30px)"
13714
+ };
12775
13715
  var TREND_COLOR = {
12776
- up: "var(--iris-success, #16a34a)",
13716
+ up: "var(--iris-success, #10b981)",
12777
13717
  down: "var(--iris-danger)",
12778
13718
  neutral: "var(--iris-muted)"
12779
13719
  };
12780
13720
  var TREND_ARROW = { up: "\u25B2", down: "\u25BC", neutral: "" };
13721
+ var TREND_TONE = {
13722
+ success: "var(--iris-success, #10b981)",
13723
+ danger: "var(--iris-danger)",
13724
+ neutral: "var(--iris-muted)"
13725
+ };
12781
13726
  var IrisStatistic = vue.defineComponent({
12782
13727
  name: "IrisStatistic",
12783
13728
  inheritAttrs: false,
@@ -12789,6 +13734,7 @@ var IrisStatistic = vue.defineComponent({
12789
13734
  description: { type: [String, Number], default: void 0 },
12790
13735
  trend: { type: String, default: void 0 },
12791
13736
  trendValue: { type: [String, Number], default: void 0 },
13737
+ trendTone: { type: String, default: void 0 },
12792
13738
  size: { type: String, default: "md" }
12793
13739
  },
12794
13740
  setup(props, { attrs }) {
@@ -12811,7 +13757,7 @@ var IrisStatistic = vue.defineComponent({
12811
13757
  "div",
12812
13758
  {
12813
13759
  "data-iris-statistic-label": "",
12814
- style: { fontSize: "13px", color: "var(--iris-muted)" }
13760
+ style: { fontSize: "var(--iris-font-size-sm, 13px)", color: "var(--iris-muted)" }
12815
13761
  },
12816
13762
  String(props.label)
12817
13763
  ) : null,
@@ -12851,8 +13797,8 @@ var IrisStatistic = vue.defineComponent({
12851
13797
  display: "inline-flex",
12852
13798
  alignItems: "center",
12853
13799
  gap: "4px",
12854
- fontSize: "13px",
12855
- color: props.trend ? TREND_COLOR[props.trend] : "var(--iris-muted)"
13800
+ fontSize: "var(--iris-font-size-sm, 13px)",
13801
+ color: props.trend ? props.trendTone ? TREND_TONE[props.trendTone] : TREND_COLOR[props.trend] : "var(--iris-muted)"
12856
13802
  }
12857
13803
  },
12858
13804
  [
@@ -12864,7 +13810,7 @@ var IrisStatistic = vue.defineComponent({
12864
13810
  "div",
12865
13811
  {
12866
13812
  "data-iris-statistic-desc": "",
12867
- style: { fontSize: "12px", color: "var(--iris-muted)" }
13813
+ style: { fontSize: "var(--iris-font-size-xs, 12px)", color: "var(--iris-muted)" }
12868
13814
  },
12869
13815
  String(props.description)
12870
13816
  ) : null
@@ -12890,7 +13836,7 @@ var IrisDescriptions = vue.defineComponent({
12890
13836
  const pad3 = props.bordered ? "8px 12px" : void 0;
12891
13837
  const labelStyle = {
12892
13838
  margin: "0",
12893
- fontSize: "13px",
13839
+ fontSize: "var(--iris-font-size-sm, 13px)",
12894
13840
  fontWeight: "500",
12895
13841
  color: "var(--iris-muted)",
12896
13842
  padding: pad3,
@@ -12898,7 +13844,7 @@ var IrisDescriptions = vue.defineComponent({
12898
13844
  };
12899
13845
  const valueStyle = {
12900
13846
  margin: "0",
12901
- fontSize: "14px",
13847
+ fontSize: "var(--iris-font-size-md, 14px)",
12902
13848
  color: "var(--iris-foreground)",
12903
13849
  padding: pad3
12904
13850
  };
@@ -12990,7 +13936,12 @@ var IrisTransfer = vue.defineComponent({
12990
13936
  titles: { type: Array, default: void 0 },
12991
13937
  /** Show a search box per pane. */
12992
13938
  searchable: { type: Boolean, default: false },
12993
- disabled: { type: Boolean, default: false }
13939
+ disabled: { type: Boolean, default: false },
13940
+ /**
13941
+ * Opt-in windowed rendering of both pane lists via the core virtualizer
13942
+ * (10k+ options stay smooth). Off by default — the plain path is unchanged.
13943
+ */
13944
+ virtual: { type: Object, default: void 0 }
12994
13945
  },
12995
13946
  emits: {
12996
13947
  "update:modelValue": (_values) => true
@@ -13053,9 +14004,30 @@ var IrisTransfer = vue.defineComponent({
13053
14004
  background: "var(--iris-background)",
13054
14005
  color: "var(--iris-foreground)",
13055
14006
  cursor: "pointer",
13056
- fontSize: "16px",
14007
+ fontSize: "var(--iris-font-size-lg, 16px)",
13057
14008
  lineHeight: "1"
13058
14009
  };
14010
+ const listStyle = {
14011
+ listStyle: "none",
14012
+ margin: "0",
14013
+ padding: "4px",
14014
+ maxHeight: "200px",
14015
+ overflowY: "auto",
14016
+ flex: "1"
14017
+ };
14018
+ const emptyRow = vue.h(
14019
+ "li",
14020
+ {
14021
+ "data-iris-transfer-empty": "",
14022
+ style: {
14023
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
14024
+ color: "var(--iris-muted)",
14025
+ fontSize: "var(--iris-font-size-sm, 13px)",
14026
+ textAlign: "center"
14027
+ }
14028
+ },
14029
+ t("transfer.empty")
14030
+ );
13059
14031
  const renderPane = (side) => {
13060
14032
  const items = (side === "source" ? sourceItems : targetItems).value;
13061
14033
  const model = side === "source" ? sourceModel : targetModel;
@@ -13072,6 +14044,34 @@ var IrisTransfer = vue.defineComponent({
13072
14044
  if (allChecked) model.clear();
13073
14045
  else model.set(selectable.map((o) => o.value));
13074
14046
  };
14047
+ const renderRow = (o, tag) => vue.h(tag, { key: o.value, "data-iris-transfer-item": "", "data-value": o.value }, [
14048
+ vue.h(
14049
+ "label",
14050
+ {
14051
+ style: {
14052
+ display: "flex",
14053
+ alignItems: "center",
14054
+ gap: "8px",
14055
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)",
14056
+ borderRadius: "var(--iris-radius-sm, 4px)",
14057
+ cursor: o.disabled ? "not-allowed" : "pointer",
14058
+ opacity: o.disabled ? "0.5" : "1",
14059
+ fontSize: "var(--iris-font-size-md, 14px)"
14060
+ }
14061
+ },
14062
+ [
14063
+ vue.h("input", {
14064
+ type: "checkbox",
14065
+ checked: checked.has(o.value),
14066
+ disabled: props.disabled || o.disabled || void 0,
14067
+ onChange: () => toggle(o.value)
14068
+ }),
14069
+ vue.h("span", o.label)
14070
+ ]
14071
+ )
14072
+ ]);
14073
+ const virtualHeight = props.virtual?.height ?? 200;
14074
+ const virtualized = props.virtual !== void 0 && visible.length > 0;
13075
14075
  return vue.h("div", { "data-iris-transfer-pane": "", "data-side": side, style: paneStyle }, [
13076
14076
  vue.h(
13077
14077
  "div",
@@ -13081,10 +14081,10 @@ var IrisTransfer = vue.defineComponent({
13081
14081
  display: "flex",
13082
14082
  alignItems: "center",
13083
14083
  justifyContent: "space-between",
13084
- gap: "8px",
13085
- padding: "6px 10px",
14084
+ gap: "var(--iris-space-xs, 8px)",
14085
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
13086
14086
  borderBlockEnd: "1px solid var(--iris-border)",
13087
- fontSize: "13px",
14087
+ fontSize: "var(--iris-font-size-sm, 13px)",
13088
14088
  fontWeight: "500"
13089
14089
  }
13090
14090
  },
@@ -13092,7 +14092,12 @@ var IrisTransfer = vue.defineComponent({
13092
14092
  vue.h(
13093
14093
  "label",
13094
14094
  {
13095
- style: { display: "inline-flex", alignItems: "center", gap: "6px", minWidth: "0" }
14095
+ style: {
14096
+ display: "inline-flex",
14097
+ alignItems: "center",
14098
+ gap: "var(--iris-space-xs, 8px)",
14099
+ minWidth: "0"
14100
+ }
13096
14101
  },
13097
14102
  [
13098
14103
  vue.h("input", {
@@ -13112,7 +14117,7 @@ var IrisTransfer = vue.defineComponent({
13112
14117
  "span",
13113
14118
  {
13114
14119
  "data-iris-transfer-count": "",
13115
- style: { color: "var(--iris-muted)", fontSize: "12px" }
14120
+ style: { color: "var(--iris-muted)", fontSize: "var(--iris-font-size-xs, 12px)" }
13116
14121
  },
13117
14122
  `${checkedCount}/${items.length}`
13118
14123
  )
@@ -13129,9 +14134,9 @@ var IrisTransfer = vue.defineComponent({
13129
14134
  queryRef.value = e.target.value;
13130
14135
  },
13131
14136
  style: {
13132
- margin: "8px",
13133
- padding: "4px 8px",
13134
- fontSize: "13px",
14137
+ margin: "var(--iris-space-xs, 8px)",
14138
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)",
14139
+ fontSize: "var(--iris-font-size-sm, 13px)",
13135
14140
  border: "1px solid var(--iris-border)",
13136
14141
  borderRadius: "var(--iris-radius-sm, 4px)",
13137
14142
  background: "var(--iris-background)",
@@ -13139,61 +14144,31 @@ var IrisTransfer = vue.defineComponent({
13139
14144
  outline: "none"
13140
14145
  }
13141
14146
  }) : null,
13142
- vue.h(
13143
- "ul",
14147
+ virtualized ? vue.h(
14148
+ IrisVirtualScroll,
13144
14149
  {
14150
+ items: visible,
14151
+ itemHeight: props.virtual.itemHeight,
14152
+ height: virtualHeight,
14153
+ buffer: props.virtual.buffer,
14154
+ keyOf: (o) => String(o.value),
13145
14155
  "data-iris-transfer-list": "",
13146
14156
  style: {
13147
- listStyle: "none",
13148
- margin: "0",
13149
- padding: "4px",
13150
- maxHeight: "200px",
13151
- overflowY: "auto",
13152
- flex: "1"
14157
+ flex: "1",
14158
+ maxHeight: typeof virtualHeight === "number" ? `${virtualHeight}px` : virtualHeight,
14159
+ boxSizing: "content-box"
13153
14160
  }
13154
14161
  },
13155
- visible.length === 0 ? [
13156
- vue.h(
13157
- "li",
13158
- {
13159
- "data-iris-transfer-empty": "",
13160
- style: {
13161
- padding: "8px 10px",
13162
- color: "var(--iris-muted)",
13163
- fontSize: "13px",
13164
- textAlign: "center"
13165
- }
13166
- },
13167
- t("transfer.empty")
13168
- )
13169
- ] : visible.map(
13170
- (o) => vue.h("li", { key: o.value, "data-iris-transfer-item": "", "data-value": o.value }, [
13171
- vue.h(
13172
- "label",
13173
- {
13174
- style: {
13175
- display: "flex",
13176
- alignItems: "center",
13177
- gap: "8px",
13178
- padding: "4px 8px",
13179
- borderRadius: "var(--iris-radius-sm, 4px)",
13180
- cursor: o.disabled ? "not-allowed" : "pointer",
13181
- opacity: o.disabled ? "0.5" : "1",
13182
- fontSize: "14px"
13183
- }
13184
- },
13185
- [
13186
- vue.h("input", {
13187
- type: "checkbox",
13188
- checked: checked.has(o.value),
13189
- disabled: props.disabled || o.disabled || void 0,
13190
- onChange: () => toggle(o.value)
13191
- }),
13192
- vue.h("span", o.label)
13193
- ]
13194
- )
13195
- ])
13196
- )
14162
+ {
14163
+ item: ({ item }) => renderRow(item, "div")
14164
+ }
14165
+ ) : vue.h(
14166
+ "ul",
14167
+ {
14168
+ "data-iris-transfer-list": "",
14169
+ style: listStyle
14170
+ },
14171
+ visible.length === 0 ? [emptyRow] : visible.map((o) => renderRow(o, "li"))
13197
14172
  )
13198
14173
  ]);
13199
14174
  };
@@ -13395,11 +14370,11 @@ var IrisBackTop = vue.defineComponent({
13395
14370
  background: "var(--iris-surface, var(--iris-background))",
13396
14371
  color: "var(--iris-foreground)",
13397
14372
  cursor: "pointer",
13398
- boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
14373
+ boxShadow: "var(--iris-shadow-md)",
13399
14374
  display: "inline-flex",
13400
14375
  alignItems: "center",
13401
14376
  justifyContent: "center",
13402
- fontSize: "18px",
14377
+ fontSize: "var(--iris-font-size-xl, 18px)",
13403
14378
  ...attrs.style ?? {}
13404
14379
  }
13405
14380
  },
@@ -13494,7 +14469,7 @@ var IrisAffix = vue.defineComponent({
13494
14469
  }
13495
14470
  });
13496
14471
  var STATUS = {
13497
- success: { color: "var(--iris-success, #16a34a)", glyph: "\u2713" },
14472
+ success: { color: "var(--iris-success, #10b981)", glyph: "\u2713" },
13498
14473
  error: { color: "var(--iris-danger)", glyph: "\u2715" },
13499
14474
  info: { color: "var(--iris-info, #0ea5e9)", glyph: "i" },
13500
14475
  warning: { color: "var(--iris-warning, #f59e0b)", glyph: "!" }
@@ -13523,9 +14498,9 @@ var IrisResult = vue.defineComponent({
13523
14498
  display: "inline-flex",
13524
14499
  alignItems: "center",
13525
14500
  justifyContent: "center",
13526
- fontSize: "30px",
14501
+ fontSize: "var(--iris-font-size-4xl, 30px)",
13527
14502
  fontWeight: "700",
13528
- color: "#fff",
14503
+ color: "var(--iris-primary-foreground, #fff)",
13529
14504
  background: s.color,
13530
14505
  marginBlockEnd: "8px"
13531
14506
  }
@@ -13536,7 +14511,11 @@ var IrisResult = vue.defineComponent({
13536
14511
  "div",
13537
14512
  {
13538
14513
  "data-iris-result-title": "",
13539
- style: { fontSize: "20px", fontWeight: "600", color: "var(--iris-foreground)" }
14514
+ style: {
14515
+ fontSize: "var(--iris-font-size-2xl, 20px)",
14516
+ fontWeight: "600",
14517
+ color: "var(--iris-foreground)"
14518
+ }
13540
14519
  },
13541
14520
  props.title
13542
14521
  ) : null,
@@ -13544,7 +14523,11 @@ var IrisResult = vue.defineComponent({
13544
14523
  "div",
13545
14524
  {
13546
14525
  "data-iris-result-subtitle": "",
13547
- style: { fontSize: "14px", color: "var(--iris-muted)", maxWidth: "480px" }
14526
+ style: {
14527
+ fontSize: "var(--iris-font-size-md, 14px)",
14528
+ color: "var(--iris-muted)",
14529
+ maxWidth: "480px"
14530
+ }
13548
14531
  },
13549
14532
  props.subtitle
13550
14533
  ) : null,
@@ -13590,9 +14573,21 @@ var IrisResult = vue.defineComponent({
13590
14573
  }
13591
14574
  });
13592
14575
  var SIZE_MAP7 = {
13593
- sm: { padding: "4px 8px", fontSize: "12px", minHeight: "28px" },
13594
- md: { padding: "6px 12px", fontSize: "14px", minHeight: "34px" },
13595
- lg: { padding: "8px 12px", fontSize: "16px", minHeight: "40px" }
14576
+ sm: {
14577
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)",
14578
+ fontSize: "var(--iris-font-size-xs, 12px)",
14579
+ minHeight: "28px"
14580
+ },
14581
+ md: {
14582
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
14583
+ fontSize: "var(--iris-font-size-md, 14px)",
14584
+ minHeight: "34px"
14585
+ },
14586
+ lg: {
14587
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
14588
+ fontSize: "var(--iris-font-size-lg, 16px)",
14589
+ minHeight: "40px"
14590
+ }
13596
14591
  };
13597
14592
  function findNode(nodes, value) {
13598
14593
  for (const n of nodes) {
@@ -13695,7 +14690,7 @@ var IrisTreeSelect = vue.defineComponent({
13695
14690
  background: "transparent",
13696
14691
  cursor: "pointer",
13697
14692
  color: "var(--iris-muted)",
13698
- fontSize: "10px"
14693
+ fontSize: "var(--iris-font-size-xs, 12px)"
13699
14694
  }
13700
14695
  },
13701
14696
  isExpanded ? "\u25BE" : "\u25B8"
@@ -13804,7 +14799,10 @@ var IrisTreeSelect = vue.defineComponent({
13804
14799
  ),
13805
14800
  vue.h(
13806
14801
  "span",
13807
- { "aria-hidden": "true", style: { color: "var(--iris-muted)", fontSize: "10px" } },
14802
+ {
14803
+ "aria-hidden": "true",
14804
+ style: { color: "var(--iris-muted)", fontSize: "var(--iris-font-size-xs, 12px)" }
14805
+ },
13808
14806
  "\u25BE"
13809
14807
  )
13810
14808
  ]
@@ -13828,7 +14826,7 @@ var IrisTreeSelect = vue.defineComponent({
13828
14826
  background: "var(--iris-background)",
13829
14827
  border: "1px solid var(--iris-border)",
13830
14828
  borderRadius: "var(--iris-radius-md, 6px)",
13831
- boxShadow: "0 8px 24px rgba(0,0,0,0.12)"
14829
+ boxShadow: "var(--iris-shadow-lg)"
13832
14830
  }
13833
14831
  },
13834
14832
  renderNodes(props.options, 0, sz)
@@ -13851,8 +14849,8 @@ function detect(text, caret, prefix) {
13851
14849
  var TEXTAREA_STYLE = {
13852
14850
  boxSizing: "border-box",
13853
14851
  width: "100%",
13854
- padding: "8px 12px",
13855
- fontSize: "14px",
14852
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
14853
+ fontSize: "var(--iris-font-size-md, 14px)",
13856
14854
  fontFamily: "inherit",
13857
14855
  color: "var(--iris-foreground)",
13858
14856
  background: "var(--iris-background)",
@@ -13875,8 +14873,10 @@ var LIST_STYLE = {
13875
14873
  background: "var(--iris-background)",
13876
14874
  border: "1px solid var(--iris-border)",
13877
14875
  borderRadius: "var(--iris-radius-md, 6px)",
13878
- boxShadow: "0 8px 24px rgba(0,0,0,0.12)"
14876
+ boxShadow: "var(--iris-shadow-lg)"
13879
14877
  };
14878
+ var LISTBOX_MAX_HEIGHT2 = 200;
14879
+ var ROW_HEIGHT2 = 32;
13880
14880
  var IrisMentions = vue.defineComponent({
13881
14881
  name: "IrisMentions",
13882
14882
  inheritAttrs: false,
@@ -13889,6 +14889,13 @@ var IrisMentions = vue.defineComponent({
13889
14889
  disabled: { type: Boolean, default: false },
13890
14890
  invalid: { type: Boolean, default: false },
13891
14891
  rows: { type: Number, default: 3 },
14892
+ /**
14893
+ * Opt-in windowed rendering of the suggestion listbox via the core
14894
+ * virtualizer. When true, only the visible window (+ buffer) of options is
14895
+ * rendered; keyboard navigation scrolls the active option into view and
14896
+ * every keystroke re-anchors the window to the top. Default false.
14897
+ */
14898
+ virtual: { type: Boolean, default: false },
13892
14899
  id: { type: String, default: void 0 },
13893
14900
  ariaDescribedby: { type: String, default: void 0 }
13894
14901
  },
@@ -13909,6 +14916,61 @@ var IrisMentions = vue.defineComponent({
13909
14916
  ) : []
13910
14917
  );
13911
14918
  const open = vue.computed(() => active.value !== null && filtered.value.length > 0);
14919
+ const listboxRef = vue.ref(null);
14920
+ const vstate = vue.ref({
14921
+ items: [],
14922
+ offsetBefore: 0,
14923
+ totalSize: 0,
14924
+ startIndex: 0,
14925
+ endIndex: -1
14926
+ });
14927
+ let v = null;
14928
+ let unsub = null;
14929
+ let lastText;
14930
+ let lastActiveIndex = 0;
14931
+ vue.watchEffect(() => {
14932
+ if (!props.virtual) return;
14933
+ if (!v) {
14934
+ v = core.createVirtualizer({
14935
+ count: 0,
14936
+ estimateSize: () => ROW_HEIGHT2,
14937
+ getItemKey: (i) => filtered.value[i]?.value ?? i,
14938
+ viewportSize: LISTBOX_MAX_HEIGHT2,
14939
+ buffer: 4
14940
+ });
14941
+ vstate.value = v.getState();
14942
+ unsub = v.subscribe((s) => {
14943
+ vstate.value = s;
14944
+ });
14945
+ }
14946
+ const list = filtered.value;
14947
+ v.setCount(list.length);
14948
+ const idx = activeIndex.value;
14949
+ if (props.modelValue !== lastText) {
14950
+ lastText = props.modelValue;
14951
+ lastActiveIndex = 0;
14952
+ v.setScroll(0);
14953
+ const el2 = listboxRef.value;
14954
+ if (el2) el2.scrollTop = 0;
14955
+ return;
14956
+ }
14957
+ const el = listboxRef.value;
14958
+ if (el) {
14959
+ const max = Math.max(0, v.totalSize() - LISTBOX_MAX_HEIGHT2);
14960
+ if (el.scrollTop > max) el.scrollTop = max;
14961
+ }
14962
+ if (idx !== lastActiveIndex) {
14963
+ lastActiveIndex = idx;
14964
+ if (idx >= 0 && idx < list.length && el) {
14965
+ const top = el.scrollTop;
14966
+ const start = idx * ROW_HEIGHT2;
14967
+ if (start < top || start + ROW_HEIGHT2 > top + LISTBOX_MAX_HEIGHT2) {
14968
+ el.scrollTop = v.scrollToIndex(idx, start < top ? "start" : "end");
14969
+ }
14970
+ }
14971
+ }
14972
+ });
14973
+ vue.onBeforeUnmount(() => unsub?.());
13912
14974
  const onInput = (e) => {
13913
14975
  const ta = e.target;
13914
14976
  const val = ta.value;
@@ -13959,6 +15021,7 @@ var IrisMentions = vue.defineComponent({
13959
15021
  };
13960
15022
  return () => {
13961
15023
  const activeId = open.value ? `${reactId}-opt-${activeIndex.value}` : void 0;
15024
+ const list = filtered.value;
13962
15025
  return vue.h(
13963
15026
  "div",
13964
15027
  {
@@ -14002,9 +15065,63 @@ var IrisMentions = vue.defineComponent({
14002
15065
  id: listboxId,
14003
15066
  role: "listbox",
14004
15067
  "data-iris-mentions-listbox": "",
15068
+ ref: (el) => {
15069
+ listboxRef.value = el ?? null;
15070
+ },
15071
+ onScroll: (e) => {
15072
+ v?.setScroll(e.currentTarget.scrollTop);
15073
+ },
14005
15074
  style: LIST_STYLE
14006
15075
  },
14007
- filtered.value.map(
15076
+ props.virtual && v && vstate.value.items.length > 0 ? [
15077
+ vue.h("li", {
15078
+ role: "presentation",
15079
+ "aria-hidden": "true",
15080
+ "data-iris-mentions-spacer": "",
15081
+ "data-iris-mentions-spacer-type": "top",
15082
+ style: { height: `${vstate.value.offsetBefore}px` }
15083
+ }),
15084
+ ...vstate.value.items.map((item) => {
15085
+ const opt = list[item.index];
15086
+ if (!opt) return null;
15087
+ const isActive = item.index === activeIndex.value;
15088
+ return vue.h(
15089
+ "li",
15090
+ {
15091
+ key: opt.value,
15092
+ id: `${reactId}-opt-${item.index}`,
15093
+ role: "option",
15094
+ "aria-selected": isActive ? "true" : "false",
15095
+ "aria-setsize": list.length,
15096
+ "aria-posinset": item.index + 1,
15097
+ "data-iris-mentions-option": "",
15098
+ "data-value": opt.value,
15099
+ onMousedown: (e) => e.preventDefault(),
15100
+ onMouseenter: () => {
15101
+ activeIndex.value = item.index;
15102
+ },
15103
+ onClick: () => insert(opt),
15104
+ style: {
15105
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
15106
+ fontSize: "var(--iris-font-size-md, 14px)",
15107
+ borderRadius: "var(--iris-radius-sm, 4px)",
15108
+ cursor: "pointer",
15109
+ background: isActive ? "var(--iris-surface-hover, rgba(99,102,241,0.1))" : "transparent"
15110
+ }
15111
+ },
15112
+ opt.label
15113
+ );
15114
+ }),
15115
+ vue.h("li", {
15116
+ role: "presentation",
15117
+ "aria-hidden": "true",
15118
+ "data-iris-mentions-spacer": "",
15119
+ "data-iris-mentions-spacer-type": "bottom",
15120
+ style: {
15121
+ height: `${vstate.value.totalSize - vstate.value.offsetBefore - vstate.value.items.length * ROW_HEIGHT2}px`
15122
+ }
15123
+ })
15124
+ ] : list.map(
14008
15125
  (opt, i) => vue.h(
14009
15126
  "li",
14010
15127
  {
@@ -14020,8 +15137,8 @@ var IrisMentions = vue.defineComponent({
14020
15137
  },
14021
15138
  onClick: () => insert(opt),
14022
15139
  style: {
14023
- padding: "6px 10px",
14024
- fontSize: "14px",
15140
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
15141
+ fontSize: "var(--iris-font-size-md, 14px)",
14025
15142
  borderRadius: "var(--iris-radius-sm, 4px)",
14026
15143
  cursor: "pointer",
14027
15144
  background: i === activeIndex.value ? "var(--iris-surface-hover, rgba(99,102,241,0.1))" : "transparent"
@@ -14037,10 +15154,25 @@ var IrisMentions = vue.defineComponent({
14037
15154
  }
14038
15155
  });
14039
15156
  var SIZE_MAP8 = {
14040
- sm: { padding: "4px 8px", fontSize: "12px", minHeight: "28px" },
14041
- md: { padding: "6px 12px", fontSize: "14px", minHeight: "34px" },
14042
- lg: { padding: "8px 12px", fontSize: "16px", minHeight: "40px" }
15157
+ sm: {
15158
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)",
15159
+ fontSize: "var(--iris-font-size-xs, 12px)",
15160
+ minHeight: "28px"
15161
+ },
15162
+ md: {
15163
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
15164
+ fontSize: "var(--iris-font-size-md, 14px)",
15165
+ minHeight: "34px"
15166
+ },
15167
+ lg: {
15168
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
15169
+ fontSize: "var(--iris-font-size-lg, 16px)",
15170
+ minHeight: "40px"
15171
+ }
14043
15172
  };
15173
+ var CASCADER_COLUMN_VIEWPORT = 240;
15174
+ var CASCADER_ROW_HEIGHT = { sm: 28, md: 34, lg: 40 };
15175
+ var CASCADER_VIRTUAL_BUFFER = 4;
14044
15176
  function pathLabels(options, path) {
14045
15177
  const labels = [];
14046
15178
  let level = options;
@@ -14074,6 +15206,7 @@ var IrisCascader = vue.defineComponent({
14074
15206
  invalid: { type: Boolean, default: false },
14075
15207
  separator: { type: String, default: " / " },
14076
15208
  size: { type: String, default: "md" },
15209
+ virtual: { type: Boolean, default: false },
14077
15210
  id: { type: String, default: void 0 },
14078
15211
  ariaDescribedby: { type: String, default: void 0 }
14079
15212
  },
@@ -14112,6 +15245,59 @@ var IrisCascader = vue.defineComponent({
14112
15245
  const columns = buildColumns(props.options, activePath.value);
14113
15246
  const labels = pathLabels(props.options, props.modelValue);
14114
15247
  const borderColor = props.invalid ? "var(--iris-danger)" : focused.value || open.value ? "var(--iris-primary)" : "var(--iris-border)";
15248
+ const renderOption = (ci, node) => {
15249
+ const isActive = activePath.value[ci] === node.value;
15250
+ const hasChildren = !!node.children && node.children.length > 0;
15251
+ return vue.h(
15252
+ "li",
15253
+ {
15254
+ key: node.value,
15255
+ role: "option",
15256
+ "aria-selected": isActive ? "true" : "false",
15257
+ "aria-disabled": node.disabled ? "true" : void 0,
15258
+ "data-iris-cascader-option": "",
15259
+ "data-value": node.value,
15260
+ onClick: () => selectOption(ci, node),
15261
+ onMouseenter: () => {
15262
+ hoveredValue.value = node.value;
15263
+ },
15264
+ onMouseleave: () => {
15265
+ if (hoveredValue.value === node.value) hoveredValue.value = null;
15266
+ },
15267
+ style: {
15268
+ display: "flex",
15269
+ alignItems: "center",
15270
+ justifyContent: "space-between",
15271
+ gap: "8px",
15272
+ padding: "var(--iris-padding-sm, 6px) var(--iris-space-sm, 12px)",
15273
+ fontSize: sz.fontSize,
15274
+ borderRadius: "var(--iris-radius-sm, 4px)",
15275
+ cursor: node.disabled ? "not-allowed" : "pointer",
15276
+ color: node.disabled ? "var(--iris-muted)" : "var(--iris-foreground)",
15277
+ background: isActive || hoveredValue.value === node.value ? "var(--iris-surface-hover, rgba(99,102,241,0.1))" : "transparent",
15278
+ ...props.virtual ? { height: "100%", boxSizing: "border-box" } : null
15279
+ }
15280
+ },
15281
+ [
15282
+ vue.h("span", node.label),
15283
+ hasChildren ? vue.h(
15284
+ "span",
15285
+ {
15286
+ "aria-hidden": "true",
15287
+ style: {
15288
+ color: "var(--iris-muted)",
15289
+ fontSize: "var(--iris-font-size-xs, 12px)"
15290
+ }
15291
+ },
15292
+ "\u203A"
15293
+ ) : null
15294
+ ]
15295
+ );
15296
+ };
15297
+ const columnStyle = (ci) => ({
15298
+ minWidth: "140px",
15299
+ borderInlineStart: ci > 0 ? "1px solid var(--iris-border)" : void 0
15300
+ });
14115
15301
  return vue.h(
14116
15302
  "div",
14117
15303
  {
@@ -14185,7 +15371,10 @@ var IrisCascader = vue.defineComponent({
14185
15371
  ),
14186
15372
  vue.h(
14187
15373
  "span",
14188
- { "aria-hidden": "true", style: { color: "var(--iris-muted)", fontSize: "10px" } },
15374
+ {
15375
+ "aria-hidden": "true",
15376
+ style: { color: "var(--iris-muted)", fontSize: "var(--iris-font-size-xs, 12px)" }
15377
+ },
14189
15378
  "\u25BE"
14190
15379
  )
14191
15380
  ]
@@ -14204,12 +15393,29 @@ var IrisCascader = vue.defineComponent({
14204
15393
  background: "var(--iris-background)",
14205
15394
  border: "1px solid var(--iris-border)",
14206
15395
  borderRadius: "var(--iris-radius-md, 6px)",
14207
- boxShadow: "0 8px 24px rgba(0,0,0,0.12)",
15396
+ boxShadow: "var(--iris-shadow-lg)",
14208
15397
  overflow: "hidden"
14209
15398
  }
14210
15399
  },
14211
15400
  columns.map(
14212
- (col, ci) => vue.h(
15401
+ (col, ci) => props.virtual ? vue.h(
15402
+ IrisVirtualScroll,
15403
+ {
15404
+ key: ci,
15405
+ items: col,
15406
+ itemHeight: CASCADER_ROW_HEIGHT[props.size],
15407
+ height: CASCADER_COLUMN_VIEWPORT,
15408
+ buffer: CASCADER_VIRTUAL_BUFFER,
15409
+ keyOf: (item) => item.value,
15410
+ role: "listbox",
15411
+ "data-iris-cascader-column": "",
15412
+ "data-level": ci,
15413
+ style: columnStyle(ci)
15414
+ },
15415
+ {
15416
+ item: ({ item }) => renderOption(ci, item)
15417
+ }
15418
+ ) : vue.h(
14213
15419
  "ul",
14214
15420
  {
14215
15421
  key: ci,
@@ -14220,57 +15426,12 @@ var IrisCascader = vue.defineComponent({
14220
15426
  listStyle: "none",
14221
15427
  margin: "0",
14222
15428
  padding: "4px",
14223
- minWidth: "140px",
15429
+ ...columnStyle(ci),
14224
15430
  maxHeight: "240px",
14225
- overflowY: "auto",
14226
- borderInlineStart: ci > 0 ? "1px solid var(--iris-border)" : void 0
15431
+ overflowY: "auto"
14227
15432
  }
14228
15433
  },
14229
- col.map((node) => {
14230
- const isActive = activePath.value[ci] === node.value;
14231
- const hasChildren = !!node.children && node.children.length > 0;
14232
- return vue.h(
14233
- "li",
14234
- {
14235
- key: node.value,
14236
- role: "option",
14237
- "aria-selected": isActive ? "true" : "false",
14238
- "aria-disabled": node.disabled ? "true" : void 0,
14239
- "data-iris-cascader-option": "",
14240
- "data-value": node.value,
14241
- onClick: () => selectOption(ci, node),
14242
- onMouseenter: () => {
14243
- hoveredValue.value = node.value;
14244
- },
14245
- onMouseleave: () => {
14246
- if (hoveredValue.value === node.value) hoveredValue.value = null;
14247
- },
14248
- style: {
14249
- display: "flex",
14250
- alignItems: "center",
14251
- justifyContent: "space-between",
14252
- gap: "8px",
14253
- padding: "6px 10px",
14254
- fontSize: sz.fontSize,
14255
- borderRadius: "var(--iris-radius-sm, 4px)",
14256
- cursor: node.disabled ? "not-allowed" : "pointer",
14257
- color: node.disabled ? "var(--iris-muted)" : "var(--iris-foreground)",
14258
- background: isActive || hoveredValue.value === node.value ? "var(--iris-surface-hover, rgba(99,102,241,0.1))" : "transparent"
14259
- }
14260
- },
14261
- [
14262
- vue.h("span", node.label),
14263
- hasChildren ? vue.h(
14264
- "span",
14265
- {
14266
- "aria-hidden": "true",
14267
- style: { color: "var(--iris-muted)", fontSize: "10px" }
14268
- },
14269
- "\u203A"
14270
- ) : null
14271
- ]
14272
- );
14273
- })
15434
+ col.map((node) => renderOption(ci, node))
14274
15435
  )
14275
15436
  )
14276
15437
  ) : null
@@ -14280,9 +15441,21 @@ var IrisCascader = vue.defineComponent({
14280
15441
  }
14281
15442
  });
14282
15443
  var SIZE_MAP9 = {
14283
- sm: { padding: "2px 8px", fontSize: "12px", height: "24px" },
14284
- md: { padding: "4px 12px", fontSize: "14px", height: "30px" },
14285
- lg: { padding: "6px 16px", fontSize: "16px", height: "36px" }
15444
+ sm: {
15445
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)",
15446
+ fontSize: "var(--iris-font-size-xs, 12px)",
15447
+ height: "24px"
15448
+ },
15449
+ md: {
15450
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-sm, 12px)",
15451
+ fontSize: "var(--iris-font-size-md, 14px)",
15452
+ height: "30px"
15453
+ },
15454
+ lg: {
15455
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-md, 16px)",
15456
+ fontSize: "var(--iris-font-size-lg, 16px)",
15457
+ height: "36px"
15458
+ }
14286
15459
  };
14287
15460
  var normalize = (options) => options.map((o) => typeof o === "string" ? { label: o, value: o } : o);
14288
15461
  var IrisSegmented = vue.defineComponent({
@@ -14342,8 +15515,8 @@ var IrisSegmented = vue.defineComponent({
14342
15515
  style: {
14343
15516
  display: props.block ? "flex" : "inline-flex",
14344
15517
  width: props.block ? "100%" : void 0,
14345
- gap: "2px",
14346
- padding: "2px",
15518
+ gap: "var(--iris-space-xxs, 4px)",
15519
+ padding: "var(--iris-space-xxs, 4px)",
14347
15520
  background: "var(--iris-surface)",
14348
15521
  borderRadius: "var(--iris-radius-md, 6px)",
14349
15522
  opacity: props.disabled ? "0.6" : "1",
@@ -14514,8 +15687,8 @@ var IrisImage = vue.defineComponent({
14514
15687
  borderRadius: "50%",
14515
15688
  border: "none",
14516
15689
  background: "rgba(0,0,0,0.5)",
14517
- color: "#fff",
14518
- fontSize: "22px",
15690
+ color: "var(--iris-primary-foreground, #fff)",
15691
+ fontSize: "var(--iris-font-size-2xl, 20px)",
14519
15692
  lineHeight: "1",
14520
15693
  cursor: "pointer"
14521
15694
  }
@@ -14538,10 +15711,10 @@ var fab = (size, primary) => ({
14538
15711
  justifyContent: "center",
14539
15712
  border: primary ? "none" : "1px solid var(--iris-border)",
14540
15713
  background: primary ? "var(--iris-primary)" : "var(--iris-background)",
14541
- color: primary ? "#fff" : "var(--iris-foreground)",
15714
+ color: primary ? "var(--iris-primary-foreground, #fff)" : "var(--iris-foreground)",
14542
15715
  cursor: "pointer",
14543
- boxShadow: "0 4px 12px rgba(0,0,0,0.18)",
14544
- fontSize: size > 44 ? "22px" : "16px",
15716
+ boxShadow: "var(--iris-shadow-md)",
15717
+ fontSize: size > 44 ? "var(--iris-font-size-2xl, 20px)" : "var(--iris-font-size-lg, 16px)",
14545
15718
  lineHeight: "1"
14546
15719
  });
14547
15720
  var IrisFloatButton = vue.defineComponent({
@@ -14738,7 +15911,7 @@ var IrisAnchor = vue.defineComponent({
14738
15911
  color: isActive ? "var(--iris-primary)" : "var(--iris-foreground)",
14739
15912
  fontWeight: isActive ? "600" : "400",
14740
15913
  textDecoration: "none",
14741
- fontSize: "14px"
15914
+ fontSize: "var(--iris-font-size-md, 14px)"
14742
15915
  }
14743
15916
  },
14744
15917
  item.title
@@ -14828,7 +16001,7 @@ var IrisCountdown = vue.defineComponent({
14828
16001
  "div",
14829
16002
  {
14830
16003
  "data-iris-countdown-title": "",
14831
- style: { fontSize: "13px", color: "var(--iris-muted)" }
16004
+ style: { fontSize: "var(--iris-font-size-sm, 13px)", color: "var(--iris-muted)" }
14832
16005
  },
14833
16006
  String(props.title)
14834
16007
  ) : null,
@@ -14862,8 +16035,8 @@ var IrisCountdown = vue.defineComponent({
14862
16035
  }
14863
16036
  });
14864
16037
  var btnBase = {
14865
- padding: "4px 12px",
14866
- fontSize: "13px",
16038
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-sm, 12px)",
16039
+ fontSize: "var(--iris-font-size-sm, 13px)",
14867
16040
  borderRadius: "var(--iris-radius-sm, 4px)",
14868
16041
  cursor: "pointer"
14869
16042
  };
@@ -14877,7 +16050,7 @@ var btnPrimary = {
14877
16050
  ...btnBase,
14878
16051
  border: "none",
14879
16052
  background: "var(--iris-primary)",
14880
- color: "#fff"
16053
+ color: "var(--iris-primary-foreground, #fff)"
14881
16054
  };
14882
16055
  var IrisTour = vue.defineComponent({
14883
16056
  name: "IrisTour",
@@ -14968,7 +16141,7 @@ var IrisTour = vue.defineComponent({
14968
16141
  height: `${sl.height + 8}px`,
14969
16142
  border: "2px solid var(--iris-primary)",
14970
16143
  borderRadius: "var(--iris-radius-sm, 4px)",
14971
- boxShadow: "0 0 0 9999px rgba(0,0,0,0.45)",
16144
+ boxShadow: "0 0 0 9999px color-mix(in srgb, var(--iris-foreground) 45%, transparent)",
14972
16145
  zIndex: "1001",
14973
16146
  pointerEvents: "none"
14974
16147
  }
@@ -14988,7 +16161,7 @@ var IrisTour = vue.defineComponent({
14988
16161
  background: "var(--iris-background)",
14989
16162
  border: "1px solid var(--iris-border)",
14990
16163
  borderRadius: "var(--iris-radius-md, 6px)",
14991
- boxShadow: "0 8px 24px rgba(0,0,0,0.18)",
16164
+ boxShadow: "var(--iris-shadow-lg)",
14992
16165
  ...cardPos
14993
16166
  }
14994
16167
  },
@@ -15006,7 +16179,7 @@ var IrisTour = vue.defineComponent({
15006
16179
  {
15007
16180
  "data-iris-tour-description": "",
15008
16181
  style: {
15009
- fontSize: "14px",
16182
+ fontSize: "var(--iris-font-size-md, 14px)",
15010
16183
  color: "var(--iris-foreground)",
15011
16184
  marginBlockEnd: "12px"
15012
16185
  }
@@ -15028,7 +16201,10 @@ var IrisTour = vue.defineComponent({
15028
16201
  "span",
15029
16202
  {
15030
16203
  "data-iris-tour-indicator": "",
15031
- style: { fontSize: "12px", color: "var(--iris-muted)" }
16204
+ style: {
16205
+ fontSize: "var(--iris-font-size-xs, 12px)",
16206
+ color: "var(--iris-muted)"
16207
+ }
15032
16208
  },
15033
16209
  t("tour.step", { current: current + 1, total })
15034
16210
  ),
@@ -15134,9 +16310,9 @@ var IrisTagInput = vue.defineComponent({
15134
16310
  style: {
15135
16311
  display: "inline-flex",
15136
16312
  alignItems: "center",
15137
- gap: "4px",
15138
- padding: "2px 6px",
15139
- fontSize: "13px",
16313
+ gap: "var(--iris-space-xxs, 4px)",
16314
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)",
16315
+ fontSize: "var(--iris-font-size-sm, 13px)",
15140
16316
  background: "var(--iris-surface)",
15141
16317
  border: "1px solid var(--iris-border)",
15142
16318
  borderRadius: "var(--iris-radius-sm, 4px)",
@@ -15158,7 +16334,7 @@ var IrisTagInput = vue.defineComponent({
15158
16334
  background: "transparent",
15159
16335
  color: "var(--iris-muted)",
15160
16336
  cursor: props.disabled ? "not-allowed" : "pointer",
15161
- fontSize: "14px",
16337
+ fontSize: "var(--iris-font-size-md, 14px)",
15162
16338
  lineHeight: "1",
15163
16339
  padding: "0"
15164
16340
  }
@@ -15194,8 +16370,8 @@ var IrisTagInput = vue.defineComponent({
15194
16370
  background: "transparent",
15195
16371
  color: "var(--iris-foreground)",
15196
16372
  fontFamily: "inherit",
15197
- fontSize: "14px",
15198
- padding: "2px 0"
16373
+ fontSize: "var(--iris-font-size-md, 14px)",
16374
+ padding: "var(--iris-space-xxs, 4px) 0"
15199
16375
  }
15200
16376
  })
15201
16377
  );
@@ -15209,14 +16385,14 @@ var IrisTagInput = vue.defineComponent({
15209
16385
  display: "flex",
15210
16386
  flexWrap: "wrap",
15211
16387
  alignItems: "center",
15212
- gap: "6px",
15213
- padding: "4px 8px",
16388
+ gap: "var(--iris-space-xs, 8px)",
16389
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)",
15214
16390
  minHeight: "34px",
15215
16391
  background: "var(--iris-background)",
15216
16392
  border: `1px solid ${borderColor}`,
15217
16393
  borderRadius: "var(--iris-radius-md, 6px)",
15218
16394
  opacity: props.disabled ? "0.6" : "1",
15219
- boxShadow: focused.value ? "0 0 0 3px rgba(99, 102, 241, 0.18)" : "none",
16395
+ boxShadow: focused.value ? "0 0 0 3px color-mix(in srgb, var(--iris-primary) 18%, transparent)" : "none",
15220
16396
  transition: "border-color 120ms ease, box-shadow 120ms ease",
15221
16397
  ...attrs.style ?? {}
15222
16398
  }
@@ -15227,9 +16403,18 @@ var IrisTagInput = vue.defineComponent({
15227
16403
  }
15228
16404
  });
15229
16405
  var SIZE_MAP10 = {
15230
- sm: { padding: "4px 8px", fontSize: "12px" },
15231
- md: { padding: "6px 12px", fontSize: "14px" },
15232
- lg: { padding: "8px 16px", fontSize: "16px" }
16406
+ sm: {
16407
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)",
16408
+ fontSize: "var(--iris-font-size-xs, 12px)"
16409
+ },
16410
+ md: {
16411
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
16412
+ fontSize: "var(--iris-font-size-md, 14px)"
16413
+ },
16414
+ lg: {
16415
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-md, 16px)",
16416
+ fontSize: "var(--iris-font-size-lg, 16px)"
16417
+ }
15233
16418
  };
15234
16419
  var IrisCopyButton = vue.defineComponent({
15235
16420
  name: "IrisCopyButton",
@@ -15284,14 +16469,14 @@ var IrisCopyButton = vue.defineComponent({
15284
16469
  style: {
15285
16470
  display: "inline-flex",
15286
16471
  alignItems: "center",
15287
- gap: "6px",
16472
+ gap: "var(--iris-space-xs, 8px)",
15288
16473
  padding: sz.padding,
15289
16474
  fontSize: sz.fontSize,
15290
16475
  fontFamily: "inherit",
15291
16476
  border: "1px solid var(--iris-border)",
15292
16477
  borderRadius: "var(--iris-radius-md, 6px)",
15293
- background: copied.value ? "var(--iris-success, #16a34a)" : "var(--iris-surface)",
15294
- color: copied.value ? "#fff" : "var(--iris-foreground)",
16478
+ background: copied.value ? "var(--iris-success, #10b981)" : "var(--iris-surface)",
16479
+ color: copied.value ? "var(--iris-primary-foreground, #fff)" : "var(--iris-foreground)",
15295
16480
  cursor: props.disabled ? "not-allowed" : "pointer",
15296
16481
  opacity: props.disabled ? "0.6" : "1",
15297
16482
  transition: "background-color 120ms ease, color 120ms ease",
@@ -15305,7 +16490,7 @@ var IrisCopyButton = vue.defineComponent({
15305
16490
  });
15306
16491
  var COLOR = {
15307
16492
  default: "var(--iris-primary)",
15308
- success: "var(--iris-success, #16a34a)",
16493
+ success: "var(--iris-success, #10b981)",
15309
16494
  danger: "var(--iris-danger)",
15310
16495
  warning: "var(--iris-warning, #f59e0b)"
15311
16496
  };
@@ -15410,9 +16595,21 @@ var IrisProgressCircle = vue.defineComponent({
15410
16595
  }
15411
16596
  });
15412
16597
  var SIZE_MAP11 = {
15413
- sm: { padding: "4px 10px", fontSize: "12px", height: "28px" },
15414
- md: { padding: "6px 14px", fontSize: "14px", height: "34px" },
15415
- lg: { padding: "8px 18px", fontSize: "16px", height: "40px" }
16598
+ sm: {
16599
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-sm, 12px)",
16600
+ fontSize: "var(--iris-font-size-xs, 12px)",
16601
+ height: "28px"
16602
+ },
16603
+ md: {
16604
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-md, 16px)",
16605
+ fontSize: "var(--iris-font-size-md, 14px)",
16606
+ height: "34px"
16607
+ },
16608
+ lg: {
16609
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-lg, 20px)",
16610
+ fontSize: "var(--iris-font-size-lg, 16px)",
16611
+ height: "40px"
16612
+ }
15416
16613
  };
15417
16614
  var IrisSplitButton = vue.defineComponent({
15418
16615
  name: "IrisSplitButton",
@@ -15505,9 +16702,9 @@ var IrisSplitButton = vue.defineComponent({
15505
16702
  style: {
15506
16703
  ...colors,
15507
16704
  borderInlineStart: props.variant === "primary" ? "1px solid rgba(255,255,255,0.3)" : "1px solid var(--iris-border)",
15508
- padding: "0 8px",
16705
+ padding: "0 var(--iris-space-xs, 8px)",
15509
16706
  minHeight: sz.height,
15510
- fontSize: "10px",
16707
+ fontSize: "var(--iris-font-size-xs, 12px)",
15511
16708
  borderStartEndRadius: "var(--iris-radius-md, 6px)",
15512
16709
  borderEndEndRadius: "var(--iris-radius-md, 6px)",
15513
16710
  cursor: props.disabled ? "not-allowed" : "pointer",
@@ -15541,7 +16738,7 @@ var IrisSplitButton = vue.defineComponent({
15541
16738
  background: "var(--iris-background)",
15542
16739
  border: "1px solid var(--iris-border)",
15543
16740
  borderRadius: "var(--iris-radius-md, 6px)",
15544
- boxShadow: "0 8px 24px rgba(0,0,0,0.12)"
16741
+ boxShadow: "var(--iris-shadow-lg)"
15545
16742
  }
15546
16743
  },
15547
16744
  props.actions.map(
@@ -15555,8 +16752,8 @@ var IrisSplitButton = vue.defineComponent({
15555
16752
  "data-key": a.key,
15556
16753
  onClick: () => select(a),
15557
16754
  style: {
15558
- padding: "6px 10px",
15559
- fontSize: "14px",
16755
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
16756
+ fontSize: "var(--iris-font-size-md, 14px)",
15560
16757
  borderRadius: "var(--iris-radius-sm, 4px)",
15561
16758
  cursor: a.disabled ? "not-allowed" : "pointer",
15562
16759
  color: a.disabled ? "var(--iris-muted)" : "var(--iris-foreground)"
@@ -15625,7 +16822,7 @@ var SR_ONLY = {
15625
16822
  width: "1px",
15626
16823
  height: "1px",
15627
16824
  padding: "0",
15628
- margin: "-1px",
16825
+ margin: "calc(-1px)",
15629
16826
  overflow: "hidden",
15630
16827
  clip: "rect(0, 0, 0, 0)",
15631
16828
  whiteSpace: "nowrap",
@@ -15723,11 +16920,11 @@ var IrisRibbon = vue.defineComponent({
15723
16920
  position: "absolute",
15724
16921
  insetBlockStart: "8px",
15725
16922
  background: props.color ?? "var(--iris-primary)",
15726
- color: "#fff",
15727
- padding: "2px 10px",
15728
- fontSize: "12px",
16923
+ color: "var(--iris-primary-foreground, #fff)",
16924
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-sm, 12px)",
16925
+ fontSize: "var(--iris-font-size-xs, 12px)",
15729
16926
  fontWeight: "600",
15730
- boxShadow: "0 2px 5px rgba(0,0,0,0.2)",
16927
+ boxShadow: "var(--iris-shadow-sm)",
15731
16928
  whiteSpace: "nowrap",
15732
16929
  ...side
15733
16930
  }
@@ -15741,7 +16938,7 @@ var IrisRibbon = vue.defineComponent({
15741
16938
  });
15742
16939
  var COLOR2 = {
15743
16940
  default: "var(--iris-primary)",
15744
- success: "var(--iris-success, #16a34a)",
16941
+ success: "var(--iris-success, #10b981)",
15745
16942
  danger: "var(--iris-danger)",
15746
16943
  warning: "var(--iris-warning, #f59e0b)"
15747
16944
  };
@@ -15755,6 +16952,7 @@ var IrisGauge = vue.defineComponent({
15755
16952
  /** Diameter in px. */
15756
16953
  size: { type: Number, default: 120 },
15757
16954
  strokeWidth: { type: Number, default: 10 },
16955
+ /** Semantic status (explicit by design; derive thresholds in the consumer). */
15758
16956
  status: { type: String, default: "default" },
15759
16957
  showValue: { type: Boolean, default: true },
15760
16958
  /** Custom center label given the value and rounded percent. */
@@ -15785,7 +16983,7 @@ var IrisGauge = vue.defineComponent({
15785
16983
  "aria-valuenow": props.value,
15786
16984
  "aria-valuemin": props.min,
15787
16985
  "aria-valuemax": props.max,
15788
- "aria-valuetext": `${percent}%`,
16986
+ "aria-valuetext": props.min === 0 && props.max === 100 ? `${percent}%` : `${props.value} (${percent}%)`,
15789
16987
  "aria-label": props.ariaLabel,
15790
16988
  style: {
15791
16989
  position: "relative",
@@ -15840,7 +17038,7 @@ var IrisGauge = vue.defineComponent({
15840
17038
  fontVariantNumeric: "tabular-nums"
15841
17039
  }
15842
17040
  },
15843
- props.format ? props.format(props.value, percent) : `${percent}%`
17041
+ props.format ? props.format(props.value, percent) : props.min === 0 && props.max === 100 ? `${percent}%` : String(props.value)
15844
17042
  ) : null
15845
17043
  ]
15846
17044
  );
@@ -15986,8 +17184,8 @@ var IrisFieldset = vue.defineComponent({
15986
17184
  {
15987
17185
  "data-iris-fieldset-legend": "",
15988
17186
  style: {
15989
- padding: "0 6px",
15990
- fontSize: "14px",
17187
+ padding: "0 var(--iris-space-xs, 8px)",
17188
+ fontSize: "var(--iris-font-size-md, 14px)",
15991
17189
  fontWeight: "600",
15992
17190
  color: "var(--iris-foreground)"
15993
17191
  }
@@ -15998,7 +17196,11 @@ var IrisFieldset = vue.defineComponent({
15998
17196
  "div",
15999
17197
  {
16000
17198
  "data-iris-fieldset-hint": "",
16001
- style: { fontSize: "12px", color: "var(--iris-muted)", marginBlockEnd: "8px" }
17199
+ style: {
17200
+ fontSize: "var(--iris-font-size-xs, 12px)",
17201
+ color: "var(--iris-muted)",
17202
+ marginBlockEnd: "8px"
17203
+ }
16002
17204
  },
16003
17205
  props.hint
16004
17206
  ) : null;
@@ -16115,20 +17317,26 @@ var IrisCalendar = vue.defineComponent({
16115
17317
  setup(props, { attrs, emit }) {
16116
17318
  const { t } = useI18n();
16117
17319
  const initialMonth = props.defaultMonth ?? props.modelValue ?? /* @__PURE__ */ new Date();
16118
- const visibleMonth = vue.ref(core.startOfMonth(initialMonth));
16119
- const focusDate = vue.ref(core.clampDate(props.modelValue ?? /* @__PURE__ */ new Date(), props.min, props.max));
17320
+ const nav = core.createCalendarNav({
17321
+ initialMonth: core.startOfMonth(initialMonth),
17322
+ initialFocusDate: core.clampDate(props.modelValue ?? /* @__PURE__ */ new Date(), props.min, props.max),
17323
+ weekStartsOn: props.weekStartsOn,
17324
+ min: props.min,
17325
+ max: props.max
17326
+ });
17327
+ const state = useStore(nav.store);
16120
17328
  vue.watch(
16121
17329
  () => props.modelValue,
16122
17330
  (value) => {
16123
- if (value && !core.isSameMonth(value, visibleMonth.value)) {
16124
- visibleMonth.value = core.startOfMonth(value);
16125
- focusDate.value = core.clampDate(value, props.min, props.max);
17331
+ if (value && !core.isSameMonth(value, nav.getVisibleMonth())) {
17332
+ nav.setVisibleMonth(core.startOfMonth(value));
17333
+ nav.setFocusDate(core.clampDate(value, props.min, props.max));
16126
17334
  }
16127
17335
  }
16128
17336
  );
16129
- const matrix = vue.computed(() => core.buildMonthMatrix(visibleMonth.value, props.weekStartsOn));
17337
+ const matrix = vue.computed(() => core.buildMonthMatrix(state.value.visibleMonth, props.weekStartsOn));
16130
17338
  const weekdays = vue.computed(() => core.getWeekdayNames(props.weekStartsOn, props.locale));
16131
- const title = vue.computed(() => core.formatMonthYear(visibleMonth.value, props.locale));
17339
+ const title = vue.computed(() => core.formatMonthYear(state.value.visibleMonth, props.locale));
16132
17340
  const dayLabelFmt = vue.computed(
16133
17341
  () => new Intl.DateTimeFormat(safeLocale(props.locale), {
16134
17342
  weekday: "long",
@@ -16138,17 +17346,10 @@ var IrisCalendar = vue.defineComponent({
16138
17346
  })
16139
17347
  );
16140
17348
  const goPrevMonth = () => {
16141
- visibleMonth.value = core.addMonths(visibleMonth.value, -1);
17349
+ nav.goToMonth(-1);
16142
17350
  };
16143
17351
  const goNextMonth = () => {
16144
- visibleMonth.value = core.addMonths(visibleMonth.value, 1);
16145
- };
16146
- const moveFocus = (delta) => {
16147
- const next = core.clampDate(core.addDays(focusDate.value, delta), props.min, props.max);
16148
- focusDate.value = next;
16149
- if (!core.isSameMonth(next, visibleMonth.value)) {
16150
- visibleMonth.value = core.startOfMonth(next);
16151
- }
17352
+ nav.goToMonth(1);
16152
17353
  };
16153
17354
  const selectDate = (date) => {
16154
17355
  if (props.disabled) return;
@@ -16156,60 +17357,23 @@ var IrisCalendar = vue.defineComponent({
16156
17357
  emit("update:modelValue", core.startOfDay(date));
16157
17358
  };
16158
17359
  const onGridKeyDown = (event) => {
16159
- switch (event.key) {
16160
- case "ArrowLeft":
16161
- event.preventDefault();
16162
- moveFocus(-1);
16163
- break;
16164
- case "ArrowRight":
16165
- event.preventDefault();
16166
- moveFocus(1);
16167
- break;
16168
- case "ArrowUp":
16169
- event.preventDefault();
16170
- moveFocus(-7);
16171
- break;
16172
- case "ArrowDown":
16173
- event.preventDefault();
16174
- moveFocus(7);
16175
- break;
16176
- case "Home": {
16177
- event.preventDefault();
16178
- const offset = focusDate.value.getDay() - props.weekStartsOn;
16179
- moveFocus(-((offset + 7) % 7));
16180
- break;
16181
- }
16182
- case "End": {
16183
- event.preventDefault();
16184
- const offset = (focusDate.value.getDay() - props.weekStartsOn + 7) % 7;
16185
- moveFocus(6 - offset);
16186
- break;
16187
- }
16188
- case "PageUp":
16189
- event.preventDefault();
16190
- visibleMonth.value = core.addMonths(visibleMonth.value, -1);
16191
- focusDate.value = core.clampDate(core.addMonths(focusDate.value, -1), props.min, props.max);
16192
- break;
16193
- case "PageDown":
16194
- event.preventDefault();
16195
- visibleMonth.value = core.addMonths(visibleMonth.value, 1);
16196
- focusDate.value = core.clampDate(core.addMonths(focusDate.value, 1), props.min, props.max);
16197
- break;
16198
- case "Enter":
16199
- case " ":
16200
- event.preventDefault();
16201
- selectDate(focusDate.value);
16202
- break;
17360
+ if (nav.handleKey(event.key)) {
17361
+ event.preventDefault();
17362
+ return;
17363
+ }
17364
+ if (event.key === "Enter" || event.key === " ") {
17365
+ event.preventDefault();
17366
+ selectDate(state.value.focusDate);
16203
17367
  }
16204
17368
  };
16205
17369
  const today = core.startOfDay(/* @__PURE__ */ new Date());
16206
17370
  const prevDisabled = vue.computed(() => {
16207
17371
  if (!props.min) return false;
16208
- return core.startOfMonth(visibleMonth.value) <= core.startOfMonth(props.min);
17372
+ return core.startOfMonth(state.value.visibleMonth) <= core.startOfMonth(props.min);
16209
17373
  });
16210
17374
  const nextDisabled = vue.computed(() => {
16211
17375
  if (!props.max) return false;
16212
- const endOfVisible = core.endOfMonth(visibleMonth.value);
17376
+ const endOfVisible = core.endOfMonth(state.value.visibleMonth);
16213
17377
  return core.startOfMonth(endOfVisible) >= core.startOfMonth(props.max);
16214
17378
  });
16215
17379
  const navButtonStyle = {
@@ -16279,7 +17443,7 @@ var IrisCalendar = vue.defineComponent({
16279
17443
  {
16280
17444
  "data-iris-calendar-title": "",
16281
17445
  "aria-live": "polite",
16282
- style: { fontWeight: "600", fontSize: "14px" }
17446
+ style: { fontWeight: "600", fontSize: "var(--iris-font-size-md, 14px)" }
16283
17447
  },
16284
17448
  title.value
16285
17449
  ),
@@ -16310,8 +17474,8 @@ var IrisCalendar = vue.defineComponent({
16310
17474
  style: {
16311
17475
  display: "grid",
16312
17476
  gridTemplateColumns: "repeat(7, minmax(0, 1fr))",
16313
- gap: "2px",
16314
- fontSize: "12px",
17477
+ gap: "var(--iris-space-xxs, 4px)",
17478
+ fontSize: "var(--iris-font-size-xs, 12px)",
16315
17479
  color: "var(--iris-muted)",
16316
17480
  textAlign: "center"
16317
17481
  }
@@ -16322,7 +17486,7 @@ var IrisCalendar = vue.defineComponent({
16322
17486
  {
16323
17487
  key: name,
16324
17488
  role: "columnheader",
16325
- style: { padding: "2px 0" }
17489
+ style: { padding: "var(--iris-space-xxs, 4px) 0" }
16326
17490
  },
16327
17491
  name
16328
17492
  )
@@ -16339,7 +17503,7 @@ var IrisCalendar = vue.defineComponent({
16339
17503
  style: {
16340
17504
  display: "grid",
16341
17505
  gridTemplateColumns: "repeat(7, minmax(0, 1fr))",
16342
- gap: "2px"
17506
+ gap: "var(--iris-space-xxs, 4px)"
16343
17507
  }
16344
17508
  },
16345
17509
  matrix.value.map(
@@ -16351,9 +17515,9 @@ var IrisCalendar = vue.defineComponent({
16351
17515
  "div",
16352
17516
  { key: `week-${wi}`, role: "row", style: { display: "contents" } },
16353
17517
  week.map((date) => {
16354
- const inMonth = core.isSameMonth(date, visibleMonth.value);
17518
+ const inMonth = core.isSameMonth(date, state.value.visibleMonth);
16355
17519
  const selected = props.modelValue ? core.isSameDay(date, props.modelValue) : false;
16356
- const focused = core.isSameDay(date, focusDate.value);
17520
+ const focused = core.isSameDay(date, state.value.focusDate);
16357
17521
  const isToday = core.isSameDay(date, today);
16358
17522
  const oof = core.isOutOfRange(date, props.min, props.max);
16359
17523
  const isDisabled = props.disabled || oof;
@@ -16374,11 +17538,11 @@ var IrisCalendar = vue.defineComponent({
16374
17538
  "data-outside-month": !inMonth ? "true" : void 0,
16375
17539
  disabled: isDisabled || void 0,
16376
17540
  onClick: () => {
16377
- focusDate.value = date;
17541
+ nav.setFocusDate(date);
16378
17542
  selectDate(date);
16379
17543
  },
16380
17544
  onFocus: () => {
16381
- focusDate.value = date;
17545
+ nav.setFocusDate(date);
16382
17546
  },
16383
17547
  style: {
16384
17548
  height: "32px",
@@ -16391,7 +17555,7 @@ var IrisCalendar = vue.defineComponent({
16391
17555
  borderRadius: "var(--iris-radius-sm, 4px)",
16392
17556
  cursor: isDisabled ? "not-allowed" : "pointer",
16393
17557
  opacity: isDisabled ? "0.45" : "1",
16394
- fontSize: "13px",
17558
+ fontSize: "var(--iris-font-size-sm, 13px)",
16395
17559
  fontFamily: "inherit",
16396
17560
  outline: "none"
16397
17561
  }
@@ -16471,15 +17635,15 @@ var IrisDatePicker = vue.defineComponent({
16471
17635
  style: {
16472
17636
  display: "inline-flex",
16473
17637
  alignItems: "center",
16474
- gap: "6px",
16475
- padding: "6px 12px",
17638
+ gap: "var(--iris-space-xs, 8px)",
17639
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
16476
17640
  background: "var(--iris-background)",
16477
17641
  color: props.modelValue ? "var(--iris-foreground)" : "var(--iris-muted)",
16478
17642
  border: `1px solid ${props.invalid ? "var(--iris-danger)" : "var(--iris-border)"}`,
16479
17643
  borderRadius: "var(--iris-radius-md, 6px)",
16480
17644
  cursor: props.disabled ? "not-allowed" : "pointer",
16481
17645
  opacity: props.disabled ? "0.6" : "1",
16482
- fontSize: "14px",
17646
+ fontSize: "var(--iris-font-size-md, 14px)",
16483
17647
  fontFamily: "inherit",
16484
17648
  minHeight: "34px",
16485
17649
  minWidth: "180px",
@@ -16649,7 +17813,7 @@ var IrisFileUpload = vue.defineComponent({
16649
17813
  flexDirection: "column",
16650
17814
  alignItems: "center",
16651
17815
  justifyContent: "center",
16652
- gap: "6px",
17816
+ gap: "var(--iris-space-xs, 8px)",
16653
17817
  padding: "var(--iris-padding-lg, 20px)",
16654
17818
  border: `2px dashed ${dragOver.value ? "var(--iris-primary)" : "var(--iris-border)"}`,
16655
17819
  borderRadius: "var(--iris-radius-md, 6px)",
@@ -16687,7 +17851,7 @@ var IrisFileUpload = vue.defineComponent({
16687
17851
  width: "1px",
16688
17852
  height: "1px",
16689
17853
  padding: "0",
16690
- margin: "-1px",
17854
+ margin: "calc(-1px)",
16691
17855
  overflow: "hidden",
16692
17856
  clip: "rect(0,0,0,0)",
16693
17857
  whiteSpace: "nowrap",
@@ -16715,7 +17879,7 @@ var IrisFileUpload = vue.defineComponent({
16715
17879
  "div",
16716
17880
  {
16717
17881
  "data-iris-file-upload-label": "",
16718
- style: { fontSize: "14px", fontWeight: "500" }
17882
+ style: { fontSize: "var(--iris-font-size-md, 14px)", fontWeight: "500" }
16719
17883
  },
16720
17884
  resolvedLabel.value
16721
17885
  ),
@@ -16723,7 +17887,7 @@ var IrisFileUpload = vue.defineComponent({
16723
17887
  "div",
16724
17888
  {
16725
17889
  style: {
16726
- fontSize: "12px",
17890
+ fontSize: "var(--iris-font-size-xs, 12px)",
16727
17891
  color: "var(--iris-muted)"
16728
17892
  }
16729
17893
  },
@@ -16755,18 +17919,23 @@ var IrisFileUpload = vue.defineComponent({
16755
17919
  display: "flex",
16756
17920
  alignItems: "center",
16757
17921
  gap: "8px",
16758
- padding: "6px 10px",
17922
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
16759
17923
  background: "var(--iris-surface)",
16760
17924
  border: "1px solid var(--iris-border)",
16761
17925
  borderRadius: "var(--iris-radius-sm, 4px)",
16762
- fontSize: "13px"
17926
+ fontSize: "var(--iris-font-size-sm, 13px)"
16763
17927
  }
16764
17928
  },
16765
17929
  [
16766
17930
  vue.h("span", { style: { flex: "1", minWidth: "0" } }, item.name),
16767
17931
  vue.h(
16768
17932
  "span",
16769
- { style: { color: "var(--iris-muted)", fontSize: "12px" } },
17933
+ {
17934
+ style: {
17935
+ color: "var(--iris-muted)",
17936
+ fontSize: "var(--iris-font-size-xs, 12px)"
17937
+ }
17938
+ },
16770
17939
  formatBytes(item.size)
16771
17940
  ),
16772
17941
  vue.h(
@@ -16784,9 +17953,9 @@ var IrisFileUpload = vue.defineComponent({
16784
17953
  border: "none",
16785
17954
  color: "var(--iris-muted)",
16786
17955
  cursor: props.disabled ? "not-allowed" : "pointer",
16787
- fontSize: "16px",
17956
+ fontSize: "var(--iris-font-size-lg, 16px)",
16788
17957
  lineHeight: "1",
16789
- padding: "0 4px"
17958
+ padding: "0 var(--iris-space-xxs, 4px)"
16790
17959
  }
16791
17960
  },
16792
17961
  "\xD7"
@@ -16949,7 +18118,7 @@ var IrisRangeSlider = vue.defineComponent({
16949
18118
  style: {
16950
18119
  position: "relative",
16951
18120
  width: "100%",
16952
- padding: "14px 8px",
18121
+ padding: "var(--iris-space-md, 16px) var(--iris-space-xs, 8px)",
16953
18122
  ...attrs.style ?? {}
16954
18123
  }
16955
18124
  },
@@ -17404,12 +18573,12 @@ function defaultFilter(query, item) {
17404
18573
  if (!q) return 0;
17405
18574
  const haystacks = [item.label, ...item.keywords ?? []].map((s) => s.toLowerCase());
17406
18575
  let best = null;
17407
- for (const h123 of haystacks) {
18576
+ for (const h124 of haystacks) {
17408
18577
  let qi = 0;
17409
18578
  let lastIdx = -1;
17410
18579
  let score = 0;
17411
- for (let i = 0; i < h123.length && qi < q.length; i += 1) {
17412
- if (h123[i] === q[qi]) {
18580
+ for (let i = 0; i < h124.length && qi < q.length; i += 1) {
18581
+ if (h124[i] === q[qi]) {
17413
18582
  score += lastIdx === -1 ? i : i - lastIdx - 1;
17414
18583
  lastIdx = i;
17415
18584
  qi += 1;
@@ -17584,7 +18753,7 @@ var IrisCommandPalette = vue.defineComponent({
17584
18753
  color: "var(--iris-foreground)",
17585
18754
  border: "1px solid var(--iris-border)",
17586
18755
  borderRadius: "var(--iris-radius-lg, 8px)",
17587
- boxShadow: "0 20px 40px -12px rgba(0,0,0,0.25)",
18756
+ boxShadow: "var(--iris-shadow-xl)",
17588
18757
  overflow: "hidden",
17589
18758
  ...attrs.style ?? {}
17590
18759
  }
@@ -17612,11 +18781,11 @@ var IrisCommandPalette = vue.defineComponent({
17612
18781
  },
17613
18782
  style: {
17614
18783
  width: "100%",
17615
- padding: "8px 10px",
18784
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
17616
18785
  background: "transparent",
17617
18786
  border: "none",
17618
18787
  outline: "none",
17619
- fontSize: "16px",
18788
+ fontSize: "var(--iris-font-size-lg, 16px)",
17620
18789
  fontFamily: "inherit",
17621
18790
  color: "inherit"
17622
18791
  }
@@ -17645,7 +18814,7 @@ var IrisCommandPalette = vue.defineComponent({
17645
18814
  padding: "20px",
17646
18815
  textAlign: "center",
17647
18816
  color: "var(--iris-muted)",
17648
- fontSize: "13px"
18817
+ fontSize: "var(--iris-font-size-sm, 13px)"
17649
18818
  }
17650
18819
  },
17651
18820
  resolvedEmptyText.value
@@ -17658,11 +18827,11 @@ var IrisCommandPalette = vue.defineComponent({
17658
18827
  key: `g-${row.label}-${i}`,
17659
18828
  "data-iris-command-palette-group": "",
17660
18829
  style: {
17661
- padding: "6px 12px",
17662
- fontSize: "11px",
18830
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
18831
+ fontSize: "var(--iris-font-size-xs, 12px)",
17663
18832
  fontWeight: "600",
17664
18833
  textTransform: "uppercase",
17665
- letterSpacing: "0.04em",
18834
+ letterSpacing: "var(--iris-letter-spacing-wide, 0.04em)",
17666
18835
  color: "var(--iris-muted)"
17667
18836
  }
17668
18837
  },
@@ -17693,14 +18862,14 @@ var IrisCommandPalette = vue.defineComponent({
17693
18862
  style: {
17694
18863
  display: "flex",
17695
18864
  alignItems: "center",
17696
- gap: "10px",
17697
- padding: "8px 12px",
18865
+ gap: "var(--iris-space-sm, 12px)",
18866
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
17698
18867
  borderRadius: "var(--iris-radius-sm, 4px)",
17699
18868
  cursor: item.disabled ? "not-allowed" : "pointer",
17700
18869
  opacity: item.disabled ? "0.5" : "1",
17701
18870
  background: isActive ? "var(--iris-surface-hover)" : "transparent",
17702
18871
  color: "inherit",
17703
- fontSize: "14px"
18872
+ fontSize: "var(--iris-font-size-md, 14px)"
17704
18873
  }
17705
18874
  },
17706
18875
  [
@@ -17718,8 +18887,8 @@ var IrisCommandPalette = vue.defineComponent({
17718
18887
  {
17719
18888
  "data-iris-command-palette-shortcut": "",
17720
18889
  style: {
17721
- fontSize: "11px",
17722
- padding: "2px 6px",
18890
+ fontSize: "var(--iris-font-size-xs, 12px)",
18891
+ padding: "var(--iris-space-xxs, 4px) var(--iris-space-xs, 8px)",
17723
18892
  background: "var(--iris-background)",
17724
18893
  border: "1px solid var(--iris-border)",
17725
18894
  borderRadius: "var(--iris-radius-sm, 4px)",
@@ -17775,8 +18944,8 @@ var IrisTimePicker = vue.defineComponent({
17775
18944
  if (props.format === "24h") {
17776
18945
  return { h: value.value.hours, m: value.value.minutes };
17777
18946
  }
17778
- const h123 = value.value.hours % 12 === 0 ? 12 : value.value.hours % 12;
17779
- return { h: h123, m: value.value.minutes };
18947
+ const h124 = value.value.hours % 12 === 0 ? 12 : value.value.hours % 12;
18948
+ return { h: h124, m: value.value.minutes };
17780
18949
  });
17781
18950
  const emitNew = (next) => {
17782
18951
  emit("update:modelValue", next);
@@ -17792,16 +18961,16 @@ var IrisTimePicker = vue.defineComponent({
17792
18961
  };
17793
18962
  const toggleMeridiem = () => {
17794
18963
  const isPM = meridiem.value === "PM";
17795
- const h123 = value.value.hours % 12;
17796
- const newH24 = isPM ? h123 : h123 + 12;
18964
+ const h124 = value.value.hours % 12;
18965
+ const newH24 = isPM ? h124 : h124 + 12;
17797
18966
  emitNew({ hours: newH24, minutes: value.value.minutes });
17798
18967
  };
17799
18968
  const onHoursInput = (e) => {
17800
18969
  const v = parseInt(e.target.value || "0", 10);
17801
18970
  if (Number.isNaN(v)) return;
17802
18971
  if (props.format === "12h") {
17803
- const h123 = clamp5(v, 1, 12);
17804
- const wrap = h123 === 12 ? 0 : h123;
18972
+ const h124 = clamp5(v, 1, 12);
18973
+ const wrap = h124 === 12 ? 0 : h124;
17805
18974
  const newH24 = meridiem.value === "PM" ? wrap + 12 : wrap;
17806
18975
  setHours24(newH24);
17807
18976
  } else {
@@ -17853,12 +19022,12 @@ var IrisTimePicker = vue.defineComponent({
17853
19022
  const fieldStyle = vue.computed(() => ({
17854
19023
  width: "48px",
17855
19024
  height: "34px",
17856
- padding: "4px 6px",
19025
+ padding: "var(--iris-space-xxs, 4px) var(--iris-padding-sm, 6px)",
17857
19026
  background: "var(--iris-background)",
17858
19027
  color: "var(--iris-foreground)",
17859
19028
  border: `1px solid ${props.invalid ? "var(--iris-danger)" : "var(--iris-border)"}`,
17860
19029
  borderRadius: "var(--iris-radius-sm, 4px)",
17861
- fontSize: "15px",
19030
+ fontSize: "var(--iris-font-size-md, 14px)",
17862
19031
  fontFamily: "inherit",
17863
19032
  textAlign: "center",
17864
19033
  outline: "none"
@@ -17897,7 +19066,7 @@ var IrisTimePicker = vue.defineComponent({
17897
19066
  "span",
17898
19067
  {
17899
19068
  "aria-hidden": "true",
17900
- style: { color: "var(--iris-muted)", fontSize: "15px" }
19069
+ style: { color: "var(--iris-muted)", fontSize: "var(--iris-font-size-md, 14px)" }
17901
19070
  },
17902
19071
  ":"
17903
19072
  ),
@@ -17931,7 +19100,7 @@ var IrisTimePicker = vue.defineComponent({
17931
19100
  border: "1px solid var(--iris-border)",
17932
19101
  borderRadius: "var(--iris-radius-sm, 4px)",
17933
19102
  cursor: props.disabled ? "not-allowed" : "pointer",
17934
- fontSize: "13px",
19103
+ fontSize: "var(--iris-font-size-sm, 13px)",
17935
19104
  fontFamily: "inherit",
17936
19105
  fontWeight: "600"
17937
19106
  }
@@ -18023,14 +19192,14 @@ var IrisDateRangePicker = vue.defineComponent({
18023
19192
  style: {
18024
19193
  display: "inline-flex",
18025
19194
  alignItems: "center",
18026
- padding: "6px 12px",
19195
+ padding: "var(--iris-padding-sm, 6px) var(--iris-padding-md, 12px)",
18027
19196
  background: "var(--iris-background)",
18028
19197
  color: display.value ? "var(--iris-foreground)" : "var(--iris-muted)",
18029
19198
  border: `1px solid ${props.invalid ? "var(--iris-danger)" : "var(--iris-border)"}`,
18030
19199
  borderRadius: "var(--iris-radius-md, 6px)",
18031
19200
  cursor: props.disabled ? "not-allowed" : "pointer",
18032
19201
  opacity: props.disabled ? "0.6" : "1",
18033
- fontSize: "14px",
19202
+ fontSize: "var(--iris-font-size-md, 14px)",
18034
19203
  fontFamily: "inherit",
18035
19204
  minHeight: "34px",
18036
19205
  minWidth: "260px",
@@ -18353,8 +19522,8 @@ var IrisLoginTemplate = vue.defineComponent({
18353
19522
  background: "var(--iris-surface)",
18354
19523
  border: "1px solid var(--iris-border)",
18355
19524
  borderRadius: "var(--iris-radius-lg, 8px)",
18356
- padding: "32px",
18357
- boxShadow: "0 6px 20px -8px rgba(0, 0, 0, 0.16)"
19525
+ padding: "var(--iris-space-2xl, 32px)",
19526
+ boxShadow: "var(--iris-shadow-lg)"
18358
19527
  }
18359
19528
  },
18360
19529
  [
@@ -18364,8 +19533,8 @@ var IrisLoginTemplate = vue.defineComponent({
18364
19533
  "h1",
18365
19534
  {
18366
19535
  style: {
18367
- margin: "0 0 4px 0",
18368
- fontSize: "22px",
19536
+ margin: "0 0 var(--iris-space-xxs, 4px) 0",
19537
+ fontSize: "var(--iris-font-size-2xl, 20px)",
18369
19538
  fontWeight: "700"
18370
19539
  }
18371
19540
  },
@@ -18377,7 +19546,7 @@ var IrisLoginTemplate = vue.defineComponent({
18377
19546
  style: {
18378
19547
  margin: "0",
18379
19548
  color: "var(--iris-muted)",
18380
- fontSize: "14px"
19549
+ fontSize: "var(--iris-font-size-md, 14px)"
18381
19550
  }
18382
19551
  },
18383
19552
  props.description
@@ -18519,8 +19688,8 @@ var IrisDashboardTemplate = vue.defineComponent({
18519
19688
  "div",
18520
19689
  {
18521
19690
  style: {
18522
- padding: "8px 12px",
18523
- fontSize: "14px",
19691
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
19692
+ fontSize: "var(--iris-font-size-md, 14px)",
18524
19693
  fontWeight: "600",
18525
19694
  color: "var(--iris-foreground)",
18526
19695
  opacity: state.collapsed ? 0 : 1,
@@ -18535,7 +19704,11 @@ var IrisDashboardTemplate = vue.defineComponent({
18535
19704
  "nav",
18536
19705
  {
18537
19706
  "aria-label": "Primary",
18538
- style: { display: "flex", flexDirection: "column", gap: "2px" }
19707
+ style: {
19708
+ display: "flex",
19709
+ flexDirection: "column",
19710
+ gap: "var(--iris-space-xxs, 4px)"
19711
+ }
18539
19712
  },
18540
19713
  props.nav.map((item) => {
18541
19714
  const isActive = item.id === props.activeId;
@@ -18550,14 +19723,14 @@ var IrisDashboardTemplate = vue.defineComponent({
18550
19723
  style: {
18551
19724
  display: "flex",
18552
19725
  alignItems: "center",
18553
- gap: "10px",
18554
- padding: "8px 12px",
19726
+ gap: "var(--iris-space-sm, 12px)",
19727
+ padding: "var(--iris-space-xs, 8px) var(--iris-space-sm, 12px)",
18555
19728
  background: isActive ? "var(--iris-primary)" : "transparent",
18556
19729
  color: isActive ? "var(--iris-primary-foreground, #fff)" : "var(--iris-foreground)",
18557
19730
  border: "none",
18558
19731
  borderRadius: "var(--iris-radius-sm, 4px)",
18559
19732
  cursor: "pointer",
18560
- fontSize: "14px",
19733
+ fontSize: "var(--iris-font-size-md, 14px)",
18561
19734
  fontFamily: "inherit",
18562
19735
  textAlign: "start",
18563
19736
  outline: "none"
@@ -18597,8 +19770,8 @@ var IrisDashboardTemplate = vue.defineComponent({
18597
19770
  "div",
18598
19771
  {
18599
19772
  style: {
18600
- padding: "12px 20px",
18601
- fontSize: "16px",
19773
+ padding: "var(--iris-space-sm, 12px) var(--iris-space-lg, 20px)",
19774
+ fontSize: "var(--iris-font-size-lg, 16px)",
18602
19775
  fontWeight: "600"
18603
19776
  }
18604
19777
  },
@@ -18624,8 +19797,8 @@ var IrisDashboardTemplate = vue.defineComponent({
18624
19797
  "h3",
18625
19798
  {
18626
19799
  style: {
18627
- margin: "0 0 8px 0",
18628
- fontSize: "14px",
19800
+ margin: "0 0 var(--iris-space-xs, 8px) 0",
19801
+ fontSize: "var(--iris-font-size-md, 14px)",
18629
19802
  fontWeight: "600",
18630
19803
  color: "var(--iris-foreground)"
18631
19804
  }
@@ -18634,7 +19807,12 @@ var IrisDashboardTemplate = vue.defineComponent({
18634
19807
  ),
18635
19808
  slots[`card.${card.id}`] ? slots[`card.${card.id}`]() : card.body ? vue.h(
18636
19809
  "div",
18637
- { style: { fontSize: "13px", color: "var(--iris-muted)" } },
19810
+ {
19811
+ style: {
19812
+ fontSize: "var(--iris-font-size-sm, 13px)",
19813
+ color: "var(--iris-muted)"
19814
+ }
19815
+ },
18638
19816
  card.body
18639
19817
  ) : null
18640
19818
  ]
@@ -19064,13 +20242,11 @@ var IrisLongPress = vue.defineComponent({
19064
20242
  longpress: () => true
19065
20243
  },
19066
20244
  setup(props, { slots, emit }) {
19067
- const ctrl = core.createLongPress({
19068
- holdDelay: props.holdDelay,
19069
- onLongPress: () => {
19070
- props.onLongPress?.();
19071
- emit("longpress");
19072
- }
19073
- });
20245
+ const fire = () => {
20246
+ props.onLongPress?.();
20247
+ emit("longpress");
20248
+ };
20249
+ let ctrl = core.createLongPress({ holdDelay: props.holdDelay, onLongPress: fire });
19074
20250
  const onPointerDown = () => {
19075
20251
  if (props.disabled) return;
19076
20252
  ctrl.press();
@@ -19081,6 +20257,24 @@ var IrisLongPress = vue.defineComponent({
19081
20257
  const onPointerLeave = () => {
19082
20258
  ctrl.cancel();
19083
20259
  };
20260
+ const onPointerCancel = () => {
20261
+ ctrl.cancel();
20262
+ };
20263
+ vue.watch(
20264
+ () => props.holdDelay,
20265
+ (holdDelay) => {
20266
+ const wasPressing = ctrl.state() === "pressing";
20267
+ ctrl.cancel();
20268
+ ctrl = core.createLongPress({ holdDelay, onLongPress: fire });
20269
+ if (wasPressing) ctrl.press();
20270
+ }
20271
+ );
20272
+ vue.watch(
20273
+ () => props.disabled,
20274
+ (disabled) => {
20275
+ if (disabled) ctrl.cancel();
20276
+ }
20277
+ );
19084
20278
  vue.onBeforeUnmount(() => ctrl.cancel());
19085
20279
  return () => vue.h(
19086
20280
  "span",
@@ -19089,12 +20283,61 @@ var IrisLongPress = vue.defineComponent({
19089
20283
  style: { display: "contents" },
19090
20284
  onPointerdown: onPointerDown,
19091
20285
  onPointerup: onPointerUp,
19092
- onPointerleave: onPointerLeave
20286
+ onPointerleave: onPointerLeave,
20287
+ onPointercancel: onPointerCancel
19093
20288
  },
19094
20289
  slots.default?.()
19095
20290
  );
19096
20291
  }
19097
20292
  });
20293
+ var IrisCompose = vue.defineComponent({
20294
+ name: "IrisCompose",
20295
+ props: {
20296
+ resizable: { type: Object, default: void 0 },
20297
+ movable: { type: Object, default: void 0 },
20298
+ sortable: { type: Object, default: void 0 },
20299
+ clickOutside: { type: Object, default: void 0 },
20300
+ hotkey: { type: Object, default: void 0 }
20301
+ },
20302
+ setup(props, { slots }) {
20303
+ return () => {
20304
+ const features = {
20305
+ resizable: props.resizable,
20306
+ movable: props.movable,
20307
+ sortable: props.sortable,
20308
+ clickOutside: props.clickOutside,
20309
+ hotkey: props.hotkey
20310
+ };
20311
+ if (!core.hasComposableFeatures(features)) return slots.default?.();
20312
+ const active = core.composeFeatures(features);
20313
+ let node = slots.default?.() ?? [];
20314
+ for (const feature of active) {
20315
+ switch (feature) {
20316
+ case "hotkey":
20317
+ if (props.hotkey) node = [vue.h(IrisHotkey, props.hotkey, { default: () => node })];
20318
+ break;
20319
+ case "clickOutside":
20320
+ if (props.clickOutside)
20321
+ node = [vue.h(IrisClickOutside, props.clickOutside, { default: () => node })];
20322
+ break;
20323
+ case "sortable":
20324
+ if (props.sortable)
20325
+ node = [vue.h(IrisSortable, props.sortable, { default: () => node })];
20326
+ break;
20327
+ case "movable":
20328
+ if (props.movable)
20329
+ node = [vue.h(IrisMovable, props.movable, { default: () => node })];
20330
+ break;
20331
+ case "resizable":
20332
+ if (props.resizable)
20333
+ node = [vue.h(IrisResizable, props.resizable, { default: () => node })];
20334
+ break;
20335
+ }
20336
+ }
20337
+ return node;
20338
+ };
20339
+ }
20340
+ });
19098
20341
 
19099
20342
  Object.defineProperty(exports, "addDays", {
19100
20343
  enumerable: true,
@@ -19441,6 +20684,7 @@ exports.IrisClickOutside = IrisClickOutside;
19441
20684
  exports.IrisColorPicker = IrisColorPicker;
19442
20685
  exports.IrisCombobox = IrisCombobox;
19443
20686
  exports.IrisCommandPalette = IrisCommandPalette;
20687
+ exports.IrisCompose = IrisCompose;
19444
20688
  exports.IrisContainer = IrisContainer;
19445
20689
  exports.IrisCopyButton = IrisCopyButton;
19446
20690
  exports.IrisCountdown = IrisCountdown;