@iris-ui-kit/vue 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/LICENSE +21 -0
  2. package/dist/admin.cjs +1869 -0
  3. package/dist/admin.cjs.map +1 -0
  4. package/dist/admin.d.cts +376 -0
  5. package/dist/admin.d.ts +376 -0
  6. package/dist/admin.js +10 -0
  7. package/dist/admin.js.map +1 -0
  8. package/dist/async.cjs +76 -0
  9. package/dist/async.cjs.map +1 -0
  10. package/dist/async.d.cts +59 -0
  11. package/dist/async.d.ts +59 -0
  12. package/dist/async.js +3 -0
  13. package/dist/async.js.map +1 -0
  14. package/dist/behaviors.cjs +659 -0
  15. package/dist/behaviors.cjs.map +1 -0
  16. package/dist/behaviors.d.cts +441 -0
  17. package/dist/behaviors.d.ts +441 -0
  18. package/dist/behaviors.js +3 -0
  19. package/dist/behaviors.js.map +1 -0
  20. package/dist/chunk-3FJUCHCC.js +146 -0
  21. package/dist/chunk-3FJUCHCC.js.map +1 -0
  22. package/dist/chunk-3R3QDLJC.js +172 -0
  23. package/dist/chunk-3R3QDLJC.js.map +1 -0
  24. package/dist/chunk-4FLIW67H.js +137 -0
  25. package/dist/chunk-4FLIW67H.js.map +1 -0
  26. package/dist/chunk-4FQ7XF52.js +3 -0
  27. package/dist/chunk-4FQ7XF52.js.map +1 -0
  28. package/dist/chunk-72PZMU6R.js +99 -0
  29. package/dist/chunk-72PZMU6R.js.map +1 -0
  30. package/dist/chunk-75GSYEJS.js +17 -0
  31. package/dist/chunk-75GSYEJS.js.map +1 -0
  32. package/dist/chunk-7IOJADZC.js +38 -0
  33. package/dist/chunk-7IOJADZC.js.map +1 -0
  34. package/dist/chunk-CIZGSGXN.js +651 -0
  35. package/dist/chunk-CIZGSGXN.js.map +1 -0
  36. package/dist/chunk-F77KIPOH.js +103 -0
  37. package/dist/chunk-F77KIPOH.js.map +1 -0
  38. package/dist/chunk-FJBYAIL4.js +1318 -0
  39. package/dist/chunk-FJBYAIL4.js.map +1 -0
  40. package/dist/chunk-H7HVVPYR.js +66 -0
  41. package/dist/chunk-H7HVVPYR.js.map +1 -0
  42. package/dist/chunk-I6D5VXQK.js +167 -0
  43. package/dist/chunk-I6D5VXQK.js.map +1 -0
  44. package/dist/chunk-PPYNXNGZ.js +73 -0
  45. package/dist/chunk-PPYNXNGZ.js.map +1 -0
  46. package/dist/chunk-QLUIKEMJ.js +1197 -0
  47. package/dist/chunk-QLUIKEMJ.js.map +1 -0
  48. package/dist/chunk-ROJV6RON.js +3 -0
  49. package/dist/chunk-ROJV6RON.js.map +1 -0
  50. package/dist/chunk-S43IHHBU.js +82 -0
  51. package/dist/chunk-S43IHHBU.js.map +1 -0
  52. package/dist/chunk-TCAXRWT6.js +108 -0
  53. package/dist/chunk-TCAXRWT6.js.map +1 -0
  54. package/dist/chunk-TQB5LVOH.js +17 -0
  55. package/dist/chunk-TQB5LVOH.js.map +1 -0
  56. package/dist/chunk-VM2CXEUA.js +46 -0
  57. package/dist/chunk-VM2CXEUA.js.map +1 -0
  58. package/dist/chunk-W2JVHGFR.js +103 -0
  59. package/dist/chunk-W2JVHGFR.js.map +1 -0
  60. package/dist/chunk-WL67XCH5.js +59 -0
  61. package/dist/chunk-WL67XCH5.js.map +1 -0
  62. package/dist/chunk-XCNCIG2I.js +165 -0
  63. package/dist/chunk-XCNCIG2I.js.map +1 -0
  64. package/dist/chunk-XSLBANUV.js +16 -0
  65. package/dist/chunk-XSLBANUV.js.map +1 -0
  66. package/dist/chunk-ZY4NLITM.js +73 -0
  67. package/dist/chunk-ZY4NLITM.js.map +1 -0
  68. package/dist/data.cjs +59 -0
  69. package/dist/data.cjs.map +1 -0
  70. package/dist/data.d.cts +46 -0
  71. package/dist/data.d.ts +46 -0
  72. package/dist/data.js +4 -0
  73. package/dist/data.js.map +1 -0
  74. package/dist/error-boundary.cjs +115 -0
  75. package/dist/error-boundary.cjs.map +1 -0
  76. package/dist/error-boundary.d.cts +60 -0
  77. package/dist/error-boundary.d.ts +60 -0
  78. package/dist/error-boundary.js +4 -0
  79. package/dist/error-boundary.js.map +1 -0
  80. package/dist/floating.cjs +149 -0
  81. package/dist/floating.cjs.map +1 -0
  82. package/dist/floating.d.cts +106 -0
  83. package/dist/floating.d.ts +106 -0
  84. package/dist/floating.js +4 -0
  85. package/dist/floating.js.map +1 -0
  86. package/dist/form.cjs +144 -0
  87. package/dist/form.cjs.map +1 -0
  88. package/dist/form.d.cts +128 -0
  89. package/dist/form.d.ts +128 -0
  90. package/dist/form.js +3 -0
  91. package/dist/form.js.map +1 -0
  92. package/dist/i18n.cjs +112 -0
  93. package/dist/i18n.cjs.map +1 -0
  94. package/dist/i18n.d.cts +99 -0
  95. package/dist/i18n.d.ts +99 -0
  96. package/dist/i18n.js +3 -0
  97. package/dist/i18n.js.map +1 -0
  98. package/dist/index.cjs +18673 -0
  99. package/dist/index.cjs.map +1 -0
  100. package/dist/index.d.cts +8087 -0
  101. package/dist/index.d.ts +8087 -0
  102. package/dist/index.js +13525 -0
  103. package/dist/index.js.map +1 -0
  104. package/dist/layouts.cjs +392 -0
  105. package/dist/layouts.cjs.map +1 -0
  106. package/dist/layouts.d.cts +406 -0
  107. package/dist/layouts.d.ts +406 -0
  108. package/dist/layouts.js +5 -0
  109. package/dist/layouts.js.map +1 -0
  110. package/dist/machine.cjs +19 -0
  111. package/dist/machine.cjs.map +1 -0
  112. package/dist/machine.d.cts +18 -0
  113. package/dist/machine.d.ts +18 -0
  114. package/dist/machine.js +4 -0
  115. package/dist/machine.js.map +1 -0
  116. package/dist/modal-utils.cjs +170 -0
  117. package/dist/modal-utils.cjs.map +1 -0
  118. package/dist/modal-utils.d.cts +43 -0
  119. package/dist/modal-utils.d.ts +43 -0
  120. package/dist/modal-utils.js +3 -0
  121. package/dist/modal-utils.js.map +1 -0
  122. package/dist/motion.cjs +88 -0
  123. package/dist/motion.cjs.map +1 -0
  124. package/dist/motion.d.cts +43 -0
  125. package/dist/motion.d.ts +43 -0
  126. package/dist/motion.js +3 -0
  127. package/dist/motion.js.map +1 -0
  128. package/dist/provider.cjs +272 -0
  129. package/dist/provider.cjs.map +1 -0
  130. package/dist/provider.d.cts +130 -0
  131. package/dist/provider.d.ts +130 -0
  132. package/dist/provider.js +6 -0
  133. package/dist/provider.js.map +1 -0
  134. package/dist/resource.cjs +25 -0
  135. package/dist/resource.cjs.map +1 -0
  136. package/dist/resource.d.cts +17 -0
  137. package/dist/resource.d.ts +17 -0
  138. package/dist/resource.js +3 -0
  139. package/dist/resource.js.map +1 -0
  140. package/dist/skeletons.cjs +1659 -0
  141. package/dist/skeletons.cjs.map +1 -0
  142. package/dist/skeletons.d.cts +179 -0
  143. package/dist/skeletons.d.ts +179 -0
  144. package/dist/skeletons.js +7 -0
  145. package/dist/skeletons.js.map +1 -0
  146. package/dist/skins.cjs +138 -0
  147. package/dist/skins.cjs.map +1 -0
  148. package/dist/skins.d.cts +67 -0
  149. package/dist/skins.d.ts +67 -0
  150. package/dist/skins.js +3 -0
  151. package/dist/skins.js.map +1 -0
  152. package/dist/theme.cjs +106 -0
  153. package/dist/theme.cjs.map +1 -0
  154. package/dist/theme.d.cts +103 -0
  155. package/dist/theme.d.ts +103 -0
  156. package/dist/theme.js +3 -0
  157. package/dist/theme.js.map +1 -0
  158. package/dist/undo.cjs +51 -0
  159. package/dist/undo.cjs.map +1 -0
  160. package/dist/undo.d.cts +48 -0
  161. package/dist/undo.d.ts +48 -0
  162. package/dist/undo.js +3 -0
  163. package/dist/undo.js.map +1 -0
  164. package/package.json +59 -0
@@ -0,0 +1,1318 @@
1
+ import { useFloating, useDismiss } from './chunk-3FJUCHCC.js';
2
+ import { useMachine } from './chunk-TQB5LVOH.js';
3
+ import { findFirstElement, mergeSlotProps } from './chunk-F77KIPOH.js';
4
+ import { IrisSidebarLayout, IrisHeaderLayout } from './chunk-I6D5VXQK.js';
5
+ import { IrisThemeKey } from './chunk-72PZMU6R.js';
6
+ import { useStore } from './chunk-75GSYEJS.js';
7
+ import { useI18n } from './chunk-TCAXRWT6.js';
8
+ import { defineComponent, inject, h, computed, shallowRef, onBeforeUnmount, watch, ref, provide, useId, nextTick, Teleport, Fragment, watchEffect, toValue } from 'vue';
9
+ import { visibleNav, findNavPath, createExpansion, branchTrail, createFloatingMachine, findNavNode, isBranch, firstLeaf, matchTypeahead, nextEnabledIndex, isClosable, createAdminShell } from '@iris-ui-kit/core';
10
+ export { createTabsNav, findNavNode, findNavPath, firstLeaf, flattenNav, isBranch, isClosable, visibleNav } from '@iris-ui-kit/core';
11
+ import { defaultIconRegistry, resolveThemedIcon } from '@iris-ui-kit/icons';
12
+
13
+ var IrisIcon = defineComponent({
14
+ name: "IrisIcon",
15
+ inheritAttrs: false,
16
+ props: {
17
+ /** Semantic icon name resolved via the registry (e.g. 'check'). */
18
+ name: { type: String, required: true },
19
+ /** Width & height (number → px). */
20
+ size: { type: [Number, String], default: 24 },
21
+ /** Stroke width for line icons. */
22
+ strokeWidth: { type: Number, default: 2 },
23
+ /** Render as a filled glyph instead of a stroked line icon. */
24
+ fill: { type: Boolean, default: false },
25
+ /** Accessible title; sets role="img" + aria-label. Omit for decorative icons. */
26
+ title: { type: String, default: void 0 },
27
+ /** Registry to resolve `name` against. Defaults to the built-in set. */
28
+ registry: { type: Object, default: () => defaultIconRegistry }
29
+ },
30
+ setup(props, { attrs }) {
31
+ const themeCtx = inject(IrisThemeKey, void 0);
32
+ return () => {
33
+ const icon = resolveThemedIcon(props.registry, props.name, themeCtx?.current.value);
34
+ if (!icon) return null;
35
+ const paint = props.fill ? { fill: "currentColor" } : {
36
+ fill: "none",
37
+ stroke: "currentColor",
38
+ "stroke-width": props.strokeWidth,
39
+ "stroke-linecap": "round",
40
+ "stroke-linejoin": "round"
41
+ };
42
+ const children = icon.nodes.map((node, i) => h(node.tag, { key: i, ...node.attrs }));
43
+ if (props.title) children.unshift(h("title", props.title));
44
+ return h(
45
+ "svg",
46
+ {
47
+ ...attrs,
48
+ xmlns: "http://www.w3.org/2000/svg",
49
+ viewBox: icon.viewBox ?? "0 0 24 24",
50
+ width: props.size,
51
+ height: props.size,
52
+ ...paint,
53
+ role: props.title ? "img" : void 0,
54
+ "aria-label": props.title || void 0,
55
+ "aria-hidden": props.title ? void 0 : "true",
56
+ "data-iris-icon": props.name,
57
+ style: {
58
+ display: "inline-block",
59
+ verticalAlign: "middle",
60
+ flexShrink: 0,
61
+ ...attrs.style ?? {}
62
+ }
63
+ },
64
+ children
65
+ );
66
+ };
67
+ }
68
+ });
69
+
70
+ // src/admin/NavMenu.ts
71
+ var IrisNavMenu = defineComponent({
72
+ name: "IrisNavMenu",
73
+ inheritAttrs: false,
74
+ props: {
75
+ /** Normalized nav tree. */
76
+ items: { type: Array, required: true },
77
+ /** Key of the active leaf. */
78
+ activeKey: { type: String, default: void 0 },
79
+ /** Controlled expanded branch keys (v-model:expandedKeys). */
80
+ expandedKeys: { type: Array, default: void 0 },
81
+ /** Initial expanded keys when uncontrolled (else the active trail expands). */
82
+ defaultExpandedKeys: { type: Array, default: void 0 },
83
+ /** Icon-only rail (top-level items only). */
84
+ collapsed: { type: Boolean, default: false },
85
+ ariaLabel: { type: String, default: void 0 }
86
+ },
87
+ emits: {
88
+ select: (_key, _node) => true,
89
+ "update:expandedKeys": (_keys) => true
90
+ },
91
+ setup(props, { emit, attrs }) {
92
+ const { t } = useI18n();
93
+ const tree = computed(() => visibleNav(props.items));
94
+ const activePath = computed(
95
+ () => props.activeKey ? findNavPath(props.items, props.activeKey).map((n) => n.key) : []
96
+ );
97
+ const isControlled = computed(() => props.expandedKeys !== void 0);
98
+ const model = createExpansion({
99
+ mode: "multiple",
100
+ defaultExpanded: props.defaultExpandedKeys ?? (props.activeKey ? branchTrail(props.items, props.activeKey) : [])
101
+ });
102
+ const internalExpanded = shallowRef(model.get());
103
+ onBeforeUnmount(
104
+ model.store.subscribe((keys) => {
105
+ internalExpanded.value = keys;
106
+ })
107
+ );
108
+ const expanded = computed(
109
+ () => isControlled.value ? props.expandedKeys : internalExpanded.value
110
+ );
111
+ watch(
112
+ () => props.activeKey,
113
+ (key) => {
114
+ if (!isControlled.value) model.merge(key ? branchTrail(props.items, key) : []);
115
+ }
116
+ );
117
+ const toggle = (key) => {
118
+ if (isControlled.value) {
119
+ const cur = props.expandedKeys;
120
+ emit(
121
+ "update:expandedKeys",
122
+ cur.includes(key) ? cur.filter((k) => k !== key) : [...cur, key]
123
+ );
124
+ } else {
125
+ model.toggle(key);
126
+ emit("update:expandedKeys", model.get());
127
+ }
128
+ };
129
+ const select = (node) => {
130
+ if (node.disabled) return;
131
+ emit("select", node.key, node);
132
+ };
133
+ const hovered = ref(null);
134
+ const itemStyle = (opts) => {
135
+ const bg = opts.active ? "var(--iris-primary)" : hovered.value && !opts.disabled ? "var(--iris-surface)" : "transparent";
136
+ return {
137
+ display: "flex",
138
+ alignItems: "center",
139
+ gap: "10px",
140
+ width: "100%",
141
+ boxSizing: "border-box",
142
+ border: "none",
143
+ borderRadius: "var(--iris-radius-md, 6px)",
144
+ background: bg,
145
+ color: opts.active ? "var(--iris-primary-foreground, #fff)" : opts.trail ? "var(--iris-primary)" : "var(--iris-foreground)",
146
+ font: "inherit",
147
+ fontSize: "14px",
148
+ fontWeight: opts.active || opts.trail ? "600" : "400",
149
+ textAlign: "start",
150
+ cursor: opts.disabled ? "not-allowed" : "pointer",
151
+ opacity: opts.disabled ? "0.5" : "1",
152
+ padding: props.collapsed ? "10px" : `8px 10px 8px ${12 + opts.depth * 16}px`,
153
+ justifyContent: props.collapsed ? "center" : "flex-start"
154
+ };
155
+ };
156
+ const hoverHandlers = (key) => ({
157
+ onMouseenter: () => hovered.value = key,
158
+ onMouseleave: () => {
159
+ if (hovered.value === key) hovered.value = null;
160
+ }
161
+ });
162
+ const iconNode = (node, size = 18) => node.icon ? h(IrisIcon, { name: node.icon, size }) : null;
163
+ const badgeNode = (node) => node.badge !== void 0 && node.badge !== "" && !props.collapsed ? h(
164
+ "span",
165
+ {
166
+ "data-iris-nav-badge": "",
167
+ style: {
168
+ marginInlineStart: "auto",
169
+ fontSize: "11px",
170
+ lineHeight: "1",
171
+ padding: "2px 6px",
172
+ borderRadius: "999px",
173
+ background: "var(--iris-danger, #e5484d)",
174
+ color: "#fff"
175
+ }
176
+ },
177
+ String(node.badge)
178
+ ) : null;
179
+ const renderCollapsed = () => tree.value.map((node) => {
180
+ const branch = isBranch(node);
181
+ const active = branch ? activePath.value.includes(node.key) : node.key === props.activeKey;
182
+ return h(
183
+ "button",
184
+ {
185
+ key: node.key,
186
+ type: "button",
187
+ "data-iris-nav-item": "",
188
+ "data-key": node.key,
189
+ "data-active": active ? "true" : void 0,
190
+ "data-branch": branch ? "true" : void 0,
191
+ disabled: node.disabled,
192
+ title: node.title,
193
+ "aria-label": branch ? `${node.title} (section)` : node.title,
194
+ "aria-current": active && !branch ? "page" : void 0,
195
+ style: itemStyle({ depth: 0, active, trail: false, disabled: !!node.disabled }),
196
+ ...hoverHandlers(node.key),
197
+ onClick: () => select(branch ? firstLeaf(node) : node)
198
+ },
199
+ [iconNode(node, 20) ?? h("span", { style: { fontWeight: "700" } }, node.title.charAt(0))]
200
+ );
201
+ });
202
+ const renderItem = (node, depth) => {
203
+ const branch = isBranch(node);
204
+ const open = expanded.value.includes(node.key);
205
+ const active = node.key === props.activeKey;
206
+ const trail = branch && activePath.value.includes(node.key);
207
+ const row = h(
208
+ "button",
209
+ {
210
+ type: "button",
211
+ "data-iris-nav-item": "",
212
+ "data-key": node.key,
213
+ "data-branch": branch ? "true" : void 0,
214
+ "data-active": active ? "true" : void 0,
215
+ "data-open": branch && open ? "true" : void 0,
216
+ "data-depth": String(depth),
217
+ disabled: node.disabled,
218
+ "aria-expanded": branch ? open ? "true" : "false" : void 0,
219
+ "aria-current": active ? "page" : void 0,
220
+ style: itemStyle({ depth, active, trail, disabled: !!node.disabled }),
221
+ ...hoverHandlers(node.key),
222
+ onClick: () => branch ? toggle(node.key) : select(node)
223
+ },
224
+ [
225
+ iconNode(node),
226
+ h(
227
+ "span",
228
+ {
229
+ style: {
230
+ flex: branch ? "1" : "0 1 auto",
231
+ overflow: "hidden",
232
+ textOverflow: "ellipsis",
233
+ whiteSpace: "nowrap"
234
+ }
235
+ },
236
+ node.title
237
+ ),
238
+ badgeNode(node),
239
+ branch ? h(IrisIcon, {
240
+ name: open ? "chevron-down" : "chevron-right",
241
+ size: 16,
242
+ style: { marginInlineStart: badgeNode(node) ? "6px" : "auto", opacity: "0.6" }
243
+ }) : null
244
+ ]
245
+ );
246
+ if (!branch || !open)
247
+ return h("div", { key: node.key, "data-iris-nav-group": branch ? "" : void 0 }, [row]);
248
+ return h("div", { key: node.key, "data-iris-nav-group": "", "data-open": "true" }, [
249
+ row,
250
+ h(
251
+ "div",
252
+ { "data-iris-nav-children": "", role: "group" },
253
+ (node.children ?? []).map((child) => renderItem(child, depth + 1))
254
+ )
255
+ ]);
256
+ };
257
+ const onKeydown = (e) => {
258
+ const root = e.currentTarget;
259
+ const buttons = Array.from(root.querySelectorAll("[data-iris-nav-item]"));
260
+ if (buttons.length === 0) return;
261
+ const idx = buttons.indexOf(document.activeElement);
262
+ const focusAt = (i) => buttons[(i + buttons.length) % buttons.length]?.focus();
263
+ switch (e.key) {
264
+ case "ArrowDown":
265
+ e.preventDefault();
266
+ focusAt(idx < 0 ? 0 : idx + 1);
267
+ return;
268
+ case "ArrowUp":
269
+ e.preventDefault();
270
+ focusAt(idx < 0 ? buttons.length - 1 : idx - 1);
271
+ return;
272
+ case "Home":
273
+ e.preventDefault();
274
+ buttons[0]?.focus();
275
+ return;
276
+ case "End":
277
+ e.preventDefault();
278
+ buttons[buttons.length - 1]?.focus();
279
+ return;
280
+ }
281
+ if (props.collapsed || idx < 0) return;
282
+ const key = buttons[idx]?.getAttribute("data-key");
283
+ if (!key) return;
284
+ const node = findNavNode(props.items, key);
285
+ if (e.key === "ArrowRight" && node && isBranch(node)) {
286
+ e.preventDefault();
287
+ if (!expanded.value.includes(key)) toggle(key);
288
+ else focusAt(idx + 1);
289
+ } else if (e.key === "ArrowLeft") {
290
+ if (node && isBranch(node) && expanded.value.includes(key)) {
291
+ e.preventDefault();
292
+ toggle(key);
293
+ } else {
294
+ const parentKey = findNavPath(props.items, key).at(-2)?.key;
295
+ if (parentKey) {
296
+ e.preventDefault();
297
+ buttons.find((b) => b.getAttribute("data-key") === parentKey)?.focus();
298
+ }
299
+ }
300
+ }
301
+ };
302
+ return () => h(
303
+ "nav",
304
+ {
305
+ ...attrs,
306
+ "data-iris-nav-menu": "",
307
+ "data-collapsed": props.collapsed ? "true" : void 0,
308
+ "aria-label": props.ariaLabel ?? t("admin.nav"),
309
+ onKeydown,
310
+ style: {
311
+ display: "flex",
312
+ flexDirection: "column",
313
+ gap: "2px",
314
+ ...attrs.style ?? {}
315
+ }
316
+ },
317
+ props.collapsed ? renderCollapsed() : tree.value.map((node) => renderItem(node, 0))
318
+ );
319
+ }
320
+ });
321
+ var IrisBreadcrumb = defineComponent({
322
+ name: "IrisBreadcrumb",
323
+ inheritAttrs: false,
324
+ props: {
325
+ /** String separator. Ignored if the `separator` slot is provided. */
326
+ separator: { type: String, default: "/" }
327
+ },
328
+ setup(props, { slots, attrs }) {
329
+ const { t } = useI18n();
330
+ return () => {
331
+ const children = slots.default?.() ?? [];
332
+ const flat = [];
333
+ const walk = (nodes) => {
334
+ if (!nodes) return;
335
+ nodes.forEach((n) => {
336
+ if (n.type === Fragment) walk(n.children);
337
+ else flat.push(n);
338
+ });
339
+ };
340
+ walk(children);
341
+ const total = flat.length;
342
+ const slotsSep = slots.separator;
343
+ const sep = (key) => h(
344
+ "li",
345
+ {
346
+ key: `sep-${key}`,
347
+ "data-iris-breadcrumb-separator": "",
348
+ "aria-hidden": "true",
349
+ style: { color: "var(--iris-muted)", display: "inline-flex", alignItems: "center" }
350
+ },
351
+ slotsSep ? slotsSep() : props.separator
352
+ );
353
+ const items = [];
354
+ flat.forEach((child, i) => {
355
+ const isLast = i === total - 1;
356
+ const wrapped = h(
357
+ "li",
358
+ {
359
+ key: `item-${i}`,
360
+ "data-iris-breadcrumb-item": "",
361
+ "data-iris-breadcrumb-last": isLast ? "true" : void 0,
362
+ style: { display: "inline-flex", alignItems: "center" }
363
+ },
364
+ // Pass isCurrent=true to the last child so it self-marks aria-current.
365
+ h(
366
+ child.type,
367
+ { ...child.props ?? {}, isCurrent: isLast },
368
+ child.children
369
+ )
370
+ );
371
+ items.push(wrapped);
372
+ if (!isLast) items.push(sep(i));
373
+ });
374
+ return h(
375
+ "nav",
376
+ {
377
+ ...attrs,
378
+ "aria-label": t("breadcrumb.label"),
379
+ "data-iris-breadcrumb": ""
380
+ },
381
+ h(
382
+ "ol",
383
+ {
384
+ style: {
385
+ display: "inline-flex",
386
+ alignItems: "center",
387
+ gap: "6px",
388
+ margin: "0",
389
+ padding: "0",
390
+ listStyle: "none",
391
+ fontSize: "14px"
392
+ }
393
+ },
394
+ items
395
+ )
396
+ );
397
+ };
398
+ }
399
+ });
400
+ var IrisBreadcrumbItem = defineComponent({
401
+ name: "IrisBreadcrumbItem",
402
+ inheritAttrs: false,
403
+ props: {
404
+ href: { type: String, default: "" },
405
+ /** Internal: set by parent IrisBreadcrumb when this is the last item. */
406
+ isCurrent: { type: Boolean, default: false }
407
+ },
408
+ setup(props, { slots, attrs }) {
409
+ return () => {
410
+ const last = props.isCurrent;
411
+ const baseStyle = {
412
+ color: last ? "var(--iris-muted)" : "var(--iris-primary)",
413
+ textDecoration: "none",
414
+ cursor: last ? "default" : props.href ? "pointer" : "default"
415
+ };
416
+ const baseAttrs = {
417
+ ...attrs,
418
+ "aria-current": last ? "page" : void 0,
419
+ "data-iris-breadcrumb-crumb": "",
420
+ style: { ...baseStyle, ...attrs.style ?? {} }
421
+ };
422
+ if (props.href && !last) {
423
+ return h("a", { ...baseAttrs, href: props.href }, slots.default?.());
424
+ }
425
+ return h("span", baseAttrs, slots.default?.());
426
+ };
427
+ }
428
+ });
429
+
430
+ // src/admin/AdminBreadcrumb.ts
431
+ var IrisAdminBreadcrumb = defineComponent({
432
+ name: "IrisAdminBreadcrumb",
433
+ inheritAttrs: false,
434
+ props: {
435
+ /** Root→current ancestor chain. */
436
+ trail: { type: Array, required: true },
437
+ /** Show each node's icon before its title. */
438
+ showIcon: { type: Boolean, default: true },
439
+ /** Hide the breadcrumb when the trail has a single crumb. */
440
+ hideSingle: { type: Boolean, default: false },
441
+ separator: { type: String, default: "/" }
442
+ },
443
+ emits: {
444
+ select: (_key, _node) => true
445
+ },
446
+ setup(props, { emit, attrs }) {
447
+ return () => {
448
+ const trail = props.trail;
449
+ if (trail.length === 0 || props.hideSingle && trail.length === 1) return null;
450
+ return h(
451
+ IrisBreadcrumb,
452
+ { ...attrs, separator: props.separator },
453
+ {
454
+ default: () => trail.map((node, i) => {
455
+ const last = i === trail.length - 1;
456
+ const content = [];
457
+ if (props.showIcon && node.icon) {
458
+ content.push(
459
+ h(IrisIcon, { name: node.icon, size: 14, style: { marginInlineEnd: "4px" } })
460
+ );
461
+ }
462
+ content.push(h("span", node.title));
463
+ return h(
464
+ IrisBreadcrumbItem,
465
+ {
466
+ key: node.key,
467
+ "data-iris-admin-crumb": "",
468
+ style: {
469
+ display: "inline-flex",
470
+ alignItems: "center",
471
+ cursor: last ? "default" : "pointer"
472
+ },
473
+ onClick: last ? void 0 : () => emit("select", node.key, node)
474
+ },
475
+ { default: () => content }
476
+ );
477
+ })
478
+ }
479
+ );
480
+ };
481
+ }
482
+ });
483
+ function useTabsNav(nav) {
484
+ const state = ref(nav.getState());
485
+ const unsubscribe = nav.subscribe((next) => {
486
+ state.value = next;
487
+ });
488
+ onBeforeUnmount(unsubscribe);
489
+ return {
490
+ tabs: computed(() => state.value.tabs),
491
+ activeKey: computed(() => state.value.activeKey),
492
+ cacheKeys: computed(() => state.value.tabs.map((t) => nav.cacheKey(t.key))),
493
+ open: nav.open,
494
+ activate: nav.activate,
495
+ close: nav.close,
496
+ closeOthers: nav.closeOthers,
497
+ closeAll: nav.closeAll,
498
+ closeLeft: nav.closeLeft,
499
+ closeRight: nav.closeRight,
500
+ refresh: nav.refresh,
501
+ setPinned: nav.setPinned,
502
+ cacheKey: nav.cacheKey
503
+ };
504
+ }
505
+
506
+ // src/primitives/dropdown/context.ts
507
+ var DropdownContextKey = /* @__PURE__ */ Symbol("IrisDropdown");
508
+
509
+ // src/primitives/dropdown/Dropdown.ts
510
+ var IrisDropdown = defineComponent({
511
+ name: "IrisDropdown",
512
+ props: {
513
+ open: { type: Boolean, default: void 0 },
514
+ defaultOpen: { type: Boolean, default: false },
515
+ placement: { type: String, default: "bottom-start" },
516
+ offset: { type: Number, default: 6 }
517
+ },
518
+ emits: {
519
+ "update:open": (_value) => true
520
+ },
521
+ setup(props, { slots, emit }) {
522
+ const isControlled = computed(() => props.open !== void 0);
523
+ const machine = createFloatingMachine(props.defaultOpen ? "open" : "closed");
524
+ const { state, send } = useMachine(machine);
525
+ watch(
526
+ () => props.open,
527
+ (value) => {
528
+ if (value === void 0) return;
529
+ const currentlyOpen = state.value.value === "open";
530
+ if (value && !currentlyOpen) send({ type: "OPEN" });
531
+ if (!value && currentlyOpen) send({ type: "CLOSE" });
532
+ },
533
+ { immediate: true }
534
+ );
535
+ const open = computed(
536
+ () => isControlled.value ? Boolean(props.open) : state.value.value === "open"
537
+ );
538
+ const setOpen = (value) => {
539
+ if (!isControlled.value) send({ type: value ? "OPEN" : "CLOSE" });
540
+ emit("update:open", value);
541
+ };
542
+ provide(DropdownContextKey, {
543
+ open,
544
+ setOpen,
545
+ triggerRef: ref(null),
546
+ contentRef: ref(null),
547
+ contentId: useId(),
548
+ placement: props.placement,
549
+ offset: props.offset
550
+ });
551
+ return () => slots.default?.();
552
+ }
553
+ });
554
+ var IrisDropdownTrigger = defineComponent({
555
+ name: "IrisDropdownTrigger",
556
+ inheritAttrs: false,
557
+ props: {
558
+ asChild: { type: Boolean, default: false }
559
+ },
560
+ setup(props, { slots, attrs }) {
561
+ const ctx = inject(DropdownContextKey);
562
+ if (!ctx) {
563
+ throw new Error("[iris-ui] IrisDropdownTrigger must be a descendant of IrisDropdown");
564
+ }
565
+ const onClick = (event) => {
566
+ if (event.defaultPrevented) return;
567
+ ctx.setOpen(!ctx.open.value);
568
+ };
569
+ const onKeyDown = (event) => {
570
+ if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
571
+ event.preventDefault();
572
+ ctx.setOpen(true);
573
+ }
574
+ };
575
+ const captureRef = (el) => {
576
+ ctx.triggerRef.value = el ?? null;
577
+ };
578
+ return () => {
579
+ const triggerProps = {
580
+ "aria-haspopup": "menu",
581
+ "aria-expanded": ctx.open.value ? "true" : "false",
582
+ "aria-controls": ctx.contentId,
583
+ "data-state": ctx.open.value ? "open" : "closed",
584
+ onClick,
585
+ onKeydown: onKeyDown,
586
+ ref: captureRef
587
+ };
588
+ if (props.asChild) {
589
+ const root = findFirstElement(slots.default?.());
590
+ if (!root) {
591
+ if (typeof process === "undefined" || process.env?.NODE_ENV !== "production") {
592
+ console.warn("[iris-ui] IrisDropdownTrigger: as-child requires a single child element");
593
+ }
594
+ return null;
595
+ }
596
+ const merged = mergeSlotProps(
597
+ { ...triggerProps, ...attrs },
598
+ root.props ?? {}
599
+ );
600
+ return h(root.type, merged, root.children);
601
+ }
602
+ return h("button", { type: "button", ...attrs, ...triggerProps }, slots.default?.());
603
+ };
604
+ }
605
+ });
606
+ var IrisDropdownMenu = defineComponent({
607
+ name: "IrisDropdownMenu",
608
+ inheritAttrs: false,
609
+ props: {
610
+ teleport: {
611
+ type: [String, Object, Boolean],
612
+ default: "body"
613
+ }
614
+ },
615
+ setup(props, { slots, attrs }) {
616
+ const ctx = inject(DropdownContextKey);
617
+ if (!ctx) {
618
+ throw new Error("[iris-ui] IrisDropdownMenu must be a descendant of IrisDropdown");
619
+ }
620
+ const innerRef = ref(null);
621
+ watch(innerRef, (el) => {
622
+ ctx.contentRef.value = el;
623
+ });
624
+ const { floatingStyles } = useFloating({
625
+ anchor: ctx.triggerRef,
626
+ floating: innerRef,
627
+ open: ctx.open,
628
+ placement: ctx.placement,
629
+ offset: ctx.offset
630
+ });
631
+ useDismiss({
632
+ enabled: ctx.open,
633
+ exclude: [ctx.triggerRef, innerRef],
634
+ onDismiss: () => ctx.setOpen(false)
635
+ });
636
+ watch(ctx.open, async (open) => {
637
+ if (open) {
638
+ await nextTick();
639
+ const items = innerRef.value?.querySelectorAll('[role="menuitem"]');
640
+ items?.[0]?.focus();
641
+ } else {
642
+ ctx.triggerRef.value?.focus?.();
643
+ }
644
+ });
645
+ const typeahead = {
646
+ buffer: "",
647
+ timer: null
648
+ };
649
+ const onKeyDown = (event) => {
650
+ if (!ctx.open.value) return;
651
+ const items = Array.from(
652
+ innerRef.value?.querySelectorAll(
653
+ '[role="menuitem"]:not([aria-disabled="true"])'
654
+ ) ?? []
655
+ );
656
+ if (items.length === 0) return;
657
+ const current = document.activeElement;
658
+ const index = current ? items.indexOf(current) : -1;
659
+ switch (event.key) {
660
+ case "ArrowDown": {
661
+ event.preventDefault();
662
+ items[nextEnabledIndex(index, 1, items.length)]?.focus();
663
+ break;
664
+ }
665
+ case "ArrowUp": {
666
+ event.preventDefault();
667
+ items[nextEnabledIndex(index, -1, items.length)]?.focus();
668
+ break;
669
+ }
670
+ case "Home":
671
+ event.preventDefault();
672
+ items[0]?.focus();
673
+ break;
674
+ case "End":
675
+ event.preventDefault();
676
+ items[items.length - 1]?.focus();
677
+ break;
678
+ case "Tab":
679
+ ctx.setOpen(false);
680
+ break;
681
+ default: {
682
+ if (event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {
683
+ typeahead.buffer += event.key;
684
+ if (typeahead.timer) clearTimeout(typeahead.timer);
685
+ typeahead.timer = setTimeout(() => {
686
+ typeahead.buffer = "";
687
+ }, 500);
688
+ const match = matchTypeahead(
689
+ items.map((it) => it.textContent ?? ""),
690
+ typeahead.buffer,
691
+ index
692
+ );
693
+ if (match >= 0) {
694
+ event.preventDefault();
695
+ items[match]?.focus();
696
+ }
697
+ }
698
+ }
699
+ }
700
+ };
701
+ return () => {
702
+ if (!ctx.open.value) return null;
703
+ const menuNode = h(
704
+ "div",
705
+ {
706
+ ...attrs,
707
+ ref: (el) => {
708
+ innerRef.value = el ?? null;
709
+ },
710
+ id: ctx.contentId,
711
+ role: "menu",
712
+ tabindex: -1,
713
+ "data-iris-dropdown-menu": "",
714
+ "data-state": "open",
715
+ onKeydown: onKeyDown,
716
+ style: {
717
+ ...floatingStyles.value,
718
+ background: "var(--iris-surface)",
719
+ color: "var(--iris-foreground)",
720
+ border: "1px solid var(--iris-border)",
721
+ borderRadius: "var(--iris-radius-md)",
722
+ padding: "var(--iris-padding-sm)",
723
+ boxShadow: "0 8px 24px -8px rgba(0, 0, 0, 0.16), 0 4px 8px -2px rgba(0, 0, 0, 0.08)",
724
+ minWidth: "160px",
725
+ outline: "none",
726
+ zIndex: "1000",
727
+ ...attrs.style ?? {}
728
+ }
729
+ },
730
+ slots.default?.()
731
+ );
732
+ if (props.teleport === false) return menuNode;
733
+ return h(Teleport, { to: props.teleport }, [menuNode]);
734
+ };
735
+ }
736
+ });
737
+ var IrisDropdownItem = defineComponent({
738
+ name: "IrisDropdownItem",
739
+ inheritAttrs: false,
740
+ props: {
741
+ disabled: { type: Boolean, default: false },
742
+ /** When true, selecting does NOT close the dropdown. Useful for sub-actions. */
743
+ keepOpen: { type: Boolean, default: false }
744
+ },
745
+ emits: {
746
+ select: (_event) => true
747
+ },
748
+ setup(props, { slots, attrs, emit }) {
749
+ const ctx = inject(DropdownContextKey);
750
+ if (!ctx) {
751
+ throw new Error("[iris-ui] IrisDropdownItem must be a descendant of IrisDropdownMenu");
752
+ }
753
+ const hovered = ref(false);
754
+ const fire = (event) => {
755
+ if (props.disabled) return;
756
+ emit("select", event);
757
+ if (!props.keepOpen) ctx.setOpen(false);
758
+ };
759
+ const onClick = (event) => {
760
+ if (event.defaultPrevented) return;
761
+ fire(event);
762
+ };
763
+ const onKeyDown = (event) => {
764
+ if (event.key === "Enter" || event.key === " ") {
765
+ event.preventDefault();
766
+ fire(event);
767
+ }
768
+ };
769
+ return () => h(
770
+ "div",
771
+ {
772
+ ...attrs,
773
+ role: "menuitem",
774
+ tabindex: props.disabled ? -1 : 0,
775
+ "aria-disabled": props.disabled ? "true" : void 0,
776
+ "data-iris-dropdown-item": "",
777
+ "data-disabled": props.disabled ? "" : void 0,
778
+ onClick,
779
+ onKeydown: onKeyDown,
780
+ onPointerenter: () => hovered.value = true,
781
+ onPointerleave: () => hovered.value = false,
782
+ onFocus: () => hovered.value = true,
783
+ onBlur: () => hovered.value = false,
784
+ style: {
785
+ display: "flex",
786
+ alignItems: "center",
787
+ gap: "var(--iris-gap-sm)",
788
+ padding: "6px var(--iris-padding-md)",
789
+ borderRadius: "var(--iris-radius-sm)",
790
+ cursor: props.disabled ? "not-allowed" : "pointer",
791
+ opacity: props.disabled ? "0.5" : "1",
792
+ background: hovered.value && !props.disabled ? "var(--iris-surface-hover)" : "transparent",
793
+ color: "inherit",
794
+ outline: "none",
795
+ fontSize: "14px",
796
+ transition: "background-color 80ms ease",
797
+ ...attrs.style ?? {}
798
+ }
799
+ },
800
+ slots.default?.()
801
+ );
802
+ }
803
+ });
804
+ var IrisDropdownSeparator = defineComponent({
805
+ name: "IrisDropdownSeparator",
806
+ inheritAttrs: false,
807
+ setup(_, { attrs }) {
808
+ return () => h("div", {
809
+ ...attrs,
810
+ role: "separator",
811
+ "data-iris-dropdown-separator": "",
812
+ style: {
813
+ height: "1px",
814
+ background: "var(--iris-border)",
815
+ margin: "4px 0",
816
+ ...attrs.style ?? {}
817
+ }
818
+ });
819
+ }
820
+ });
821
+
822
+ // src/admin/AdminTabs.ts
823
+ var IrisAdminTabs = defineComponent({
824
+ name: "IrisAdminTabs",
825
+ inheritAttrs: false,
826
+ props: {
827
+ /** Shared tabs store (from `createTabsNav`). */
828
+ nav: { type: Object, required: true }
829
+ },
830
+ emits: {
831
+ change: (_key) => true,
832
+ close: (_key) => true,
833
+ refresh: (_key) => true
834
+ },
835
+ setup(props, { emit, attrs }) {
836
+ const { t: tr } = useI18n();
837
+ const t = useTabsNav(props.nav);
838
+ const activate = (key) => {
839
+ props.nav.activate(key);
840
+ emit("change", key);
841
+ };
842
+ const close = (key) => {
843
+ props.nav.close(key);
844
+ emit("close", key);
845
+ };
846
+ const refresh = (key) => {
847
+ props.nav.refresh(key);
848
+ emit("refresh", key);
849
+ };
850
+ const focusTab = (root, key) => {
851
+ if (!root || !key) return;
852
+ const labels = root.querySelectorAll("[data-iris-tab-label]");
853
+ labels.forEach((el) => {
854
+ if (el.getAttribute("data-key") === key) el.focus();
855
+ });
856
+ };
857
+ const onTablistKeydown = (e) => {
858
+ const root = e.currentTarget;
859
+ const tabs = t.tabs.value;
860
+ const idx = tabs.findIndex((x) => x.key === t.activeKey.value);
861
+ if (idx < 0 || tabs.length === 0) return;
862
+ if (e.key === "Delete" || e.key === "Backspace") {
863
+ const cur = tabs[idx];
864
+ if (cur && isClosable(cur)) {
865
+ e.preventDefault();
866
+ close(cur.key);
867
+ void nextTick(() => focusTab(root, props.nav.getState().activeKey));
868
+ }
869
+ return;
870
+ }
871
+ let target = -1;
872
+ if (e.key === "ArrowRight") target = (idx + 1) % tabs.length;
873
+ else if (e.key === "ArrowLeft") target = (idx - 1 + tabs.length) % tabs.length;
874
+ else if (e.key === "Home") target = 0;
875
+ else if (e.key === "End") target = tabs.length - 1;
876
+ else return;
877
+ e.preventDefault();
878
+ const key = tabs[target]?.key;
879
+ if (!key) return;
880
+ activate(key);
881
+ void nextTick(() => focusTab(root, key));
882
+ };
883
+ const chip = (tab) => {
884
+ const active = tab.key === t.activeKey.value;
885
+ const closable = isClosable(tab);
886
+ const labelChildren = [];
887
+ if (tab.icon) labelChildren.push(h(IrisIcon, { name: tab.icon, size: 14 }));
888
+ labelChildren.push(h("span", { style: { whiteSpace: "nowrap" } }, tab.title));
889
+ const label = h(
890
+ "button",
891
+ {
892
+ type: "button",
893
+ role: "tab",
894
+ "data-iris-tab-label": "",
895
+ "data-key": tab.key,
896
+ "aria-selected": active ? "true" : "false",
897
+ "aria-current": active ? "page" : void 0,
898
+ tabindex: active ? 0 : -1,
899
+ style: {
900
+ display: "inline-flex",
901
+ alignItems: "center",
902
+ gap: "6px",
903
+ border: "none",
904
+ background: "transparent",
905
+ color: "inherit",
906
+ font: "inherit",
907
+ fontSize: "13px",
908
+ cursor: "pointer",
909
+ padding: "0"
910
+ },
911
+ onClick: () => activate(tab.key)
912
+ },
913
+ labelChildren
914
+ );
915
+ const closeBtn = closable ? h(
916
+ "button",
917
+ {
918
+ type: "button",
919
+ tabindex: -1,
920
+ "data-iris-tab-close": "",
921
+ "aria-label": tr("admin.closeTab", { title: tab.title }),
922
+ style: {
923
+ display: "inline-flex",
924
+ alignItems: "center",
925
+ justifyContent: "center",
926
+ width: "16px",
927
+ height: "16px",
928
+ border: "none",
929
+ borderRadius: "4px",
930
+ background: "transparent",
931
+ color: "inherit",
932
+ cursor: "pointer",
933
+ padding: "0",
934
+ opacity: "0.6"
935
+ },
936
+ onClick: (e) => {
937
+ e.stopPropagation();
938
+ close(tab.key);
939
+ }
940
+ },
941
+ [h(IrisIcon, { name: "x", size: 12 })]
942
+ ) : tab.pinned ? h(IrisIcon, { name: "check-circle", size: 12, style: { opacity: "0.5" } }) : null;
943
+ return h(
944
+ "div",
945
+ {
946
+ key: tab.key,
947
+ "data-iris-tab": "",
948
+ "data-active": active ? "true" : void 0,
949
+ style: {
950
+ display: "inline-flex",
951
+ alignItems: "center",
952
+ gap: "4px",
953
+ padding: "5px 8px 5px 10px",
954
+ borderRadius: "var(--iris-radius-md, 6px)",
955
+ border: `1px solid ${active ? "var(--iris-primary)" : "var(--iris-border)"}`,
956
+ background: active ? "var(--iris-primary)" : "var(--iris-surface)",
957
+ color: active ? "var(--iris-primary-foreground, #fff)" : "var(--iris-foreground)",
958
+ flex: "0 0 auto"
959
+ }
960
+ },
961
+ [label, closeBtn]
962
+ );
963
+ };
964
+ const action = (label, run, disabled = false) => h(IrisDropdownItem, { disabled, onSelect: run }, { default: () => label });
965
+ const actionsMenu = () => h(IrisDropdown, null, {
966
+ default: () => [
967
+ h(
968
+ IrisDropdownTrigger,
969
+ { "aria-label": tr("admin.tabActions") },
970
+ {
971
+ default: () => h(
972
+ "span",
973
+ {
974
+ "data-iris-tab-actions": "",
975
+ style: {
976
+ display: "inline-flex",
977
+ alignItems: "center",
978
+ justifyContent: "center",
979
+ width: "28px",
980
+ height: "28px",
981
+ borderRadius: "var(--iris-radius-md, 6px)",
982
+ border: "1px solid var(--iris-border)",
983
+ background: "var(--iris-surface)",
984
+ color: "var(--iris-foreground)",
985
+ cursor: "pointer"
986
+ }
987
+ },
988
+ [h(IrisIcon, { name: "more-horizontal", size: 16 })]
989
+ )
990
+ }
991
+ ),
992
+ h(IrisDropdownMenu, null, {
993
+ default: () => {
994
+ const key = t.activeKey.value;
995
+ if (!key) return [action("No active tab", () => {
996
+ }, true)];
997
+ return [
998
+ action(tr("admin.refresh"), () => refresh(key)),
999
+ action(tr("admin.close"), () => close(key)),
1000
+ h(IrisDropdownSeparator),
1001
+ action(tr("admin.closeOthers"), () => props.nav.closeOthers(key)),
1002
+ action(tr("admin.closeAll"), () => props.nav.closeAll())
1003
+ ];
1004
+ }
1005
+ })
1006
+ ]
1007
+ });
1008
+ return () => h(
1009
+ "div",
1010
+ {
1011
+ ...attrs,
1012
+ "data-iris-admin-tabs": "",
1013
+ style: {
1014
+ display: "flex",
1015
+ alignItems: "center",
1016
+ gap: "6px",
1017
+ padding: "6px 10px",
1018
+ borderBottom: "1px solid var(--iris-border)",
1019
+ background: "var(--iris-background)",
1020
+ ...attrs.style ?? {}
1021
+ }
1022
+ },
1023
+ [
1024
+ h(
1025
+ "div",
1026
+ {
1027
+ "data-iris-tabs-scroll": "",
1028
+ role: "tablist",
1029
+ "aria-label": tr("admin.openPages"),
1030
+ onKeydown: onTablistKeydown,
1031
+ style: {
1032
+ display: "flex",
1033
+ alignItems: "center",
1034
+ gap: "6px",
1035
+ overflowX: "auto",
1036
+ flex: "1"
1037
+ }
1038
+ },
1039
+ t.tabs.value.map((tab) => chip(tab))
1040
+ ),
1041
+ actionsMenu()
1042
+ ]
1043
+ );
1044
+ }
1045
+ });
1046
+ function useAdminShell(config) {
1047
+ const { defaultActiveKey, onActiveKeyChange, onSelect, tabs } = config;
1048
+ const menus = () => toValue(config.menus);
1049
+ const activeKeyProp = () => toValue(config.activeKey);
1050
+ const activeControlled = () => activeKeyProp() !== void 0;
1051
+ const shell = createAdminShell({
1052
+ menus: menus(),
1053
+ defaultActiveKey: activeKeyProp() ?? defaultActiveKey,
1054
+ tabs,
1055
+ onActiveChange: (key, node) => {
1056
+ onActiveKeyChange?.(key);
1057
+ if (node) onSelect?.(key, node);
1058
+ }
1059
+ });
1060
+ const state = useStore(shell.store);
1061
+ watch(menus, (next) => shell.setMenus(next), { flush: "sync" });
1062
+ watchEffect(() => {
1063
+ const prop = activeKeyProp();
1064
+ if (prop !== void 0 && shell.getActiveKey() !== prop) {
1065
+ shell.store.setState({ activeKey: prop });
1066
+ }
1067
+ });
1068
+ const activeKey = computed(
1069
+ () => activeControlled() ? activeKeyProp() ?? "" : state.value.activeKey ?? ""
1070
+ );
1071
+ return {
1072
+ activeKey,
1073
+ navigate: shell.navigate,
1074
+ syncFromTab: shell.syncFromTab,
1075
+ breadcrumb: computed(() => activeKey.value ? findNavPath(menus(), activeKey.value) : [])
1076
+ };
1077
+ }
1078
+
1079
+ // src/admin/AdminLayout.ts
1080
+ var IrisAdminLayout = defineComponent({
1081
+ name: "IrisAdminLayout",
1082
+ inheritAttrs: false,
1083
+ props: {
1084
+ /** Normalized nav tree driving menu + breadcrumb. */
1085
+ menus: { type: Array, required: true },
1086
+ /** Active page key (v-model:activeKey). */
1087
+ activeKey: { type: String, default: void 0 },
1088
+ /** Sidebar collapsed (v-model:collapsed); undefined = uncontrolled. */
1089
+ collapsed: { type: Boolean, default: void 0 },
1090
+ defaultCollapsed: { type: Boolean, default: false },
1091
+ mode: { type: String, default: "sidebar" },
1092
+ /** Brand text shown next to the logo when expanded. */
1093
+ appTitle: { type: String, default: "Iris Admin" },
1094
+ /** Optional shared tabs store; when present the tab bar is rendered. */
1095
+ tabs: { type: Object, default: void 0 },
1096
+ showBreadcrumb: { type: Boolean, default: true },
1097
+ sidebarWidth: { type: [Number, String], default: 240 },
1098
+ collapsedWidth: { type: [Number, String], default: 64 }
1099
+ },
1100
+ emits: {
1101
+ "update:activeKey": (_key) => true,
1102
+ "update:collapsed": (_value) => true,
1103
+ select: (_key, _node) => true
1104
+ },
1105
+ setup(props, { emit, slots, attrs }) {
1106
+ const { t } = useI18n();
1107
+ const {
1108
+ activeKey,
1109
+ navigate,
1110
+ syncFromTab,
1111
+ breadcrumb: trail
1112
+ } = useAdminShell({
1113
+ menus: () => props.menus,
1114
+ activeKey: () => props.activeKey,
1115
+ tabs: props.tabs,
1116
+ onActiveKeyChange: (key) => emit("update:activeKey", key),
1117
+ onSelect: (key, node) => emit("select", key, node)
1118
+ });
1119
+ const collapseControlled = computed(() => props.collapsed !== void 0);
1120
+ const internalCollapsed = ref(props.defaultCollapsed);
1121
+ const collapsed = computed(
1122
+ () => collapseControlled.value ? props.collapsed : internalCollapsed.value
1123
+ );
1124
+ const setCollapsed = (value) => {
1125
+ if (!collapseControlled.value) internalCollapsed.value = value;
1126
+ emit("update:collapsed", value);
1127
+ };
1128
+ const onSelect = (_key, node) => navigate(node);
1129
+ if (props.tabs) {
1130
+ const tabsActive = ref(props.tabs.getState().activeKey);
1131
+ const unsubscribe = props.tabs.subscribe((s) => {
1132
+ tabsActive.value = s.activeKey;
1133
+ });
1134
+ onBeforeUnmount(unsubscribe);
1135
+ watch(tabsActive, (key) => {
1136
+ if (key) syncFromTab(key);
1137
+ });
1138
+ }
1139
+ const renderLogo = (state) => {
1140
+ if (slots.logo) return slots.logo(state);
1141
+ return h(
1142
+ "div",
1143
+ {
1144
+ "data-iris-admin-logo": "",
1145
+ style: {
1146
+ display: "flex",
1147
+ alignItems: "center",
1148
+ gap: "10px",
1149
+ height: "52px",
1150
+ padding: "0 16px",
1151
+ color: "var(--iris-foreground)",
1152
+ fontWeight: "700",
1153
+ fontSize: "16px",
1154
+ whiteSpace: "nowrap",
1155
+ overflow: "hidden",
1156
+ flexShrink: 0
1157
+ }
1158
+ },
1159
+ [
1160
+ h(
1161
+ "span",
1162
+ {
1163
+ style: {
1164
+ display: "inline-flex",
1165
+ alignItems: "center",
1166
+ justifyContent: "center",
1167
+ width: "28px",
1168
+ height: "28px",
1169
+ borderRadius: "var(--iris-radius-md, 6px)",
1170
+ background: "var(--iris-primary)",
1171
+ color: "var(--iris-primary-foreground, #fff)",
1172
+ flexShrink: 0
1173
+ }
1174
+ },
1175
+ [h(IrisIcon, { name: "menu", size: 18 })]
1176
+ ),
1177
+ state.collapsed ? null : h("span", props.appTitle)
1178
+ ]
1179
+ );
1180
+ };
1181
+ const collapseToggle = () => h(
1182
+ "button",
1183
+ {
1184
+ type: "button",
1185
+ "data-iris-admin-collapse": "",
1186
+ "aria-label": collapsed.value ? t("admin.expandSidebar") : t("admin.collapseSidebar"),
1187
+ "aria-pressed": collapsed.value ? "true" : "false",
1188
+ style: {
1189
+ display: "inline-flex",
1190
+ alignItems: "center",
1191
+ justifyContent: "center",
1192
+ width: "32px",
1193
+ height: "32px",
1194
+ border: "none",
1195
+ borderRadius: "var(--iris-radius-md, 6px)",
1196
+ background: "transparent",
1197
+ color: "var(--iris-foreground)",
1198
+ cursor: "pointer",
1199
+ flexShrink: 0
1200
+ },
1201
+ onClick: () => setCollapsed(!collapsed.value)
1202
+ },
1203
+ [h(IrisIcon, { name: collapsed.value ? "chevron-right" : "menu", size: 18 })]
1204
+ );
1205
+ const renderHeaderBar = () => h(
1206
+ "div",
1207
+ {
1208
+ "data-iris-admin-headerbar": "",
1209
+ style: {
1210
+ display: "flex",
1211
+ alignItems: "center",
1212
+ gap: "12px",
1213
+ height: "52px",
1214
+ padding: "0 16px",
1215
+ borderBottom: "1px solid var(--iris-border)",
1216
+ background: "var(--iris-background)"
1217
+ }
1218
+ },
1219
+ [
1220
+ collapseToggle(),
1221
+ props.showBreadcrumb ? h(IrisAdminBreadcrumb, { trail: trail.value, hideSingle: false, onSelect }) : null,
1222
+ h("div", { style: { flex: "1" } }),
1223
+ slots.toolbar ? h(
1224
+ "div",
1225
+ {
1226
+ "data-iris-admin-toolbar": "",
1227
+ style: { display: "flex", alignItems: "center", gap: "8px" }
1228
+ },
1229
+ slots.toolbar()
1230
+ ) : null
1231
+ ]
1232
+ );
1233
+ const renderContent = () => h(
1234
+ "div",
1235
+ { "data-iris-admin-content": "", style: { padding: "16px" } },
1236
+ slots.default?.({ activeKey: activeKey.value }) ?? []
1237
+ );
1238
+ return () => {
1239
+ if (props.mode === "full-content") {
1240
+ return h(
1241
+ "div",
1242
+ {
1243
+ ...attrs,
1244
+ "data-iris-admin-layout": "",
1245
+ "data-mode": "full-content",
1246
+ style: {
1247
+ height: "100%",
1248
+ ...attrs.style ?? {}
1249
+ }
1250
+ },
1251
+ slots.default?.({ activeKey: activeKey.value }) ?? []
1252
+ );
1253
+ }
1254
+ return h(
1255
+ IrisSidebarLayout,
1256
+ {
1257
+ ...attrs,
1258
+ collapsed: collapsed.value,
1259
+ width: props.sidebarWidth,
1260
+ collapsedWidth: props.collapsedWidth,
1261
+ "onUpdate:collapsed": setCollapsed,
1262
+ "data-iris-admin-layout": "",
1263
+ "data-mode": "sidebar",
1264
+ style: {
1265
+ height: "100vh",
1266
+ ...attrs.style ?? {}
1267
+ }
1268
+ },
1269
+ {
1270
+ sidebar: (state) => h(
1271
+ "div",
1272
+ {
1273
+ "data-iris-admin-sidebar": "",
1274
+ style: {
1275
+ display: "flex",
1276
+ flexDirection: "column",
1277
+ height: "100%",
1278
+ borderInlineEnd: "1px solid var(--iris-border)",
1279
+ background: "var(--iris-surface)"
1280
+ }
1281
+ },
1282
+ [
1283
+ renderLogo(state),
1284
+ h(
1285
+ "div",
1286
+ { style: { flex: "1", overflowY: "auto", overflowX: "hidden", padding: "8px" } },
1287
+ [
1288
+ h(IrisNavMenu, {
1289
+ items: props.menus,
1290
+ activeKey: activeKey.value,
1291
+ collapsed: state.collapsed,
1292
+ onSelect
1293
+ })
1294
+ ]
1295
+ )
1296
+ ]
1297
+ ),
1298
+ default: () => h(
1299
+ IrisHeaderLayout,
1300
+ { sticky: true },
1301
+ {
1302
+ header: () => h("div", { "data-iris-admin-header": "" }, [
1303
+ renderHeaderBar(),
1304
+ props.tabs ? h(IrisAdminTabs, { nav: props.tabs }) : null
1305
+ ]),
1306
+ default: () => renderContent(),
1307
+ ...slots.footer ? { footer: () => slots.footer() } : {}
1308
+ }
1309
+ )
1310
+ }
1311
+ );
1312
+ };
1313
+ }
1314
+ });
1315
+
1316
+ export { DropdownContextKey, IrisAdminBreadcrumb, IrisAdminLayout, IrisAdminTabs, IrisBreadcrumb, IrisBreadcrumbItem, IrisDropdown, IrisDropdownItem, IrisDropdownMenu, IrisDropdownSeparator, IrisDropdownTrigger, IrisIcon, IrisNavMenu, useAdminShell, useTabsNav };
1317
+ //# sourceMappingURL=chunk-FJBYAIL4.js.map
1318
+ //# sourceMappingURL=chunk-FJBYAIL4.js.map