@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
package/dist/admin.cjs ADDED
@@ -0,0 +1,1869 @@
1
+ 'use strict';
2
+
3
+ var vue = require('vue');
4
+ var core = require('@iris-ui-kit/core');
5
+ var icons = require('@iris-ui-kit/icons');
6
+ var theme = require('@iris-ui-kit/theme');
7
+ var dom = require('@floating-ui/dom');
8
+
9
+ // src/admin/AdminLayout.ts
10
+ var IrisSidebarLayout = vue.defineComponent({
11
+ name: "IrisSidebarLayout",
12
+ inheritAttrs: false,
13
+ props: {
14
+ collapsed: { type: Boolean, default: void 0 },
15
+ defaultCollapsed: { type: Boolean, default: false },
16
+ /** Sidebar width when expanded (px or CSS length). */
17
+ width: { type: [Number, String], default: 240 },
18
+ /** Sidebar width when collapsed. */
19
+ collapsedWidth: { type: [Number, String], default: 60 },
20
+ /** Sidebar position. */
21
+ side: { type: String, default: "left" }
22
+ },
23
+ emits: {
24
+ "update:collapsed": (_value) => true
25
+ },
26
+ setup(props, { slots, attrs, emit }) {
27
+ const isControlled = vue.computed(() => props.collapsed !== void 0);
28
+ const internalCollapsed = vue.ref(props.defaultCollapsed);
29
+ const collapsed = vue.computed(
30
+ () => isControlled.value ? Boolean(props.collapsed) : internalCollapsed.value
31
+ );
32
+ vue.watch(
33
+ () => props.collapsed,
34
+ (value) => {
35
+ if (value !== void 0) internalCollapsed.value = value;
36
+ }
37
+ );
38
+ const setCollapsed = (value) => {
39
+ if (!isControlled.value) internalCollapsed.value = value;
40
+ emit("update:collapsed", value);
41
+ };
42
+ const asLen = (v) => typeof v === "number" ? `${v}px` : v;
43
+ return () => {
44
+ const sidebarStyle = {
45
+ width: collapsed.value ? asLen(props.collapsedWidth) : asLen(props.width),
46
+ flexShrink: "0",
47
+ background: "var(--iris-surface)",
48
+ borderRight: props.side === "left" ? "1px solid var(--iris-border)" : "none",
49
+ borderLeft: props.side === "right" ? "1px solid var(--iris-border)" : "none",
50
+ transition: "width 180ms ease",
51
+ overflow: "hidden",
52
+ display: "flex",
53
+ flexDirection: "column"
54
+ };
55
+ const mainStyle = {
56
+ flex: "1",
57
+ minWidth: "0",
58
+ overflow: "auto",
59
+ background: "var(--iris-background)"
60
+ };
61
+ const direction = props.side === "right" ? "row-reverse" : "row";
62
+ return vue.h(
63
+ "div",
64
+ {
65
+ ...attrs,
66
+ "data-iris-sidebar-layout": "",
67
+ "data-collapsed": collapsed.value ? "" : void 0,
68
+ "data-side": props.side,
69
+ style: {
70
+ display: "flex",
71
+ flexDirection: direction,
72
+ width: "100%",
73
+ height: "100%",
74
+ minHeight: "0",
75
+ color: "var(--iris-foreground)",
76
+ ...attrs.style ?? {}
77
+ }
78
+ },
79
+ [
80
+ vue.h(
81
+ "aside",
82
+ {
83
+ role: "complementary",
84
+ "data-iris-sidebar": "",
85
+ "data-collapsed": collapsed.value ? "" : void 0,
86
+ style: sidebarStyle
87
+ },
88
+ slots.sidebar?.({ collapsed: collapsed.value, setCollapsed })
89
+ ),
90
+ vue.h("div", { "data-iris-sidebar-main": "", style: mainStyle }, slots.default?.())
91
+ ]
92
+ );
93
+ };
94
+ }
95
+ });
96
+ var IrisHeaderLayout = vue.defineComponent({
97
+ name: "IrisHeaderLayout",
98
+ inheritAttrs: false,
99
+ props: {
100
+ /** Header height (px or CSS length). Default `'auto'`. */
101
+ headerHeight: { type: [Number, String], default: "auto" },
102
+ /** Footer height (px or CSS length). Default `'auto'`. */
103
+ footerHeight: { type: [Number, String], default: "auto" },
104
+ /** When true, header sticks via `position: sticky` instead of static. */
105
+ sticky: { type: Boolean, default: true }
106
+ },
107
+ setup(props, { slots, attrs }) {
108
+ const asLen = (v) => typeof v === "number" ? `${v}px` : v;
109
+ return () => vue.h(
110
+ "div",
111
+ {
112
+ ...attrs,
113
+ "data-iris-header-layout": "",
114
+ style: {
115
+ display: "flex",
116
+ flexDirection: "column",
117
+ width: "100%",
118
+ height: "100%",
119
+ minHeight: "0",
120
+ background: "var(--iris-background)",
121
+ color: "var(--iris-foreground)",
122
+ ...attrs.style ?? {}
123
+ }
124
+ },
125
+ [
126
+ slots.header ? vue.h(
127
+ "header",
128
+ {
129
+ role: "banner",
130
+ "data-iris-header": "",
131
+ style: {
132
+ flexShrink: "0",
133
+ height: asLen(props.headerHeight),
134
+ borderBottom: "1px solid var(--iris-border)",
135
+ background: "var(--iris-surface)",
136
+ position: props.sticky ? "sticky" : "static",
137
+ top: "0",
138
+ zIndex: "50"
139
+ }
140
+ },
141
+ slots.header()
142
+ ) : null,
143
+ vue.h(
144
+ "main",
145
+ {
146
+ role: "main",
147
+ "data-iris-header-main": "",
148
+ style: { flex: "1", minHeight: "0", overflow: "auto" }
149
+ },
150
+ slots.default?.()
151
+ ),
152
+ slots.footer ? vue.h(
153
+ "footer",
154
+ {
155
+ role: "contentinfo",
156
+ "data-iris-footer": "",
157
+ style: {
158
+ flexShrink: "0",
159
+ height: asLen(props.footerHeight),
160
+ borderTop: "1px solid var(--iris-border)",
161
+ background: "var(--iris-surface)"
162
+ }
163
+ },
164
+ slots.footer()
165
+ ) : null
166
+ ]
167
+ );
168
+ }
169
+ });
170
+ var IrisThemeKey = /* @__PURE__ */ Symbol("IrisTheme");
171
+ vue.defineComponent({
172
+ name: "IrisThemeProvider",
173
+ props: {
174
+ store: {
175
+ type: Object,
176
+ required: true
177
+ },
178
+ target: {
179
+ type: Object,
180
+ default: null
181
+ },
182
+ /** Writing direction; sets `dir` on the target for RTL. Default `'ltr'`. */
183
+ dir: {
184
+ type: String,
185
+ default: "ltr"
186
+ },
187
+ /** CSP nonce for injected inline stylesheets. */
188
+ cspNonce: {
189
+ type: String,
190
+ default: void 0
191
+ }
192
+ },
193
+ setup(props, { slots }) {
194
+ const current = vue.ref(props.store.store.getState());
195
+ const unsubscribe = props.store.store.subscribe((next) => {
196
+ current.value = next;
197
+ });
198
+ let applied = null;
199
+ let dirApplied = null;
200
+ const targetEl = () => props.target ?? document.documentElement;
201
+ const applyCurrent = () => {
202
+ applied?.revert();
203
+ applied = theme.applyTheme(current.value, targetEl());
204
+ };
205
+ const applyDir = () => {
206
+ dirApplied?.revert();
207
+ dirApplied = theme.applyDirection(props.dir, targetEl());
208
+ };
209
+ vue.onMounted(() => {
210
+ theme.injectGlobalStyles(props.cspNonce);
211
+ applyCurrent();
212
+ applyDir();
213
+ });
214
+ vue.watch(current, () => {
215
+ if (applied) applyCurrent();
216
+ });
217
+ vue.watch(
218
+ () => props.dir,
219
+ () => {
220
+ if (dirApplied) applyDir();
221
+ }
222
+ );
223
+ vue.onBeforeUnmount(() => {
224
+ unsubscribe();
225
+ applied?.revert();
226
+ applied = null;
227
+ dirApplied?.revert();
228
+ dirApplied = null;
229
+ });
230
+ vue.provide(IrisThemeKey, { store: props.store, current, dir: vue.computed(() => props.dir) });
231
+ return () => slots.default?.();
232
+ }
233
+ });
234
+
235
+ // src/primitives/icon/Icon.ts
236
+ var IrisIcon = vue.defineComponent({
237
+ name: "IrisIcon",
238
+ inheritAttrs: false,
239
+ props: {
240
+ /** Semantic icon name resolved via the registry (e.g. 'check'). */
241
+ name: { type: String, required: true },
242
+ /** Width & height (number → px). */
243
+ size: { type: [Number, String], default: 24 },
244
+ /** Stroke width for line icons. */
245
+ strokeWidth: { type: Number, default: 2 },
246
+ /** Render as a filled glyph instead of a stroked line icon. */
247
+ fill: { type: Boolean, default: false },
248
+ /** Accessible title; sets role="img" + aria-label. Omit for decorative icons. */
249
+ title: { type: String, default: void 0 },
250
+ /** Registry to resolve `name` against. Defaults to the built-in set. */
251
+ registry: { type: Object, default: () => icons.defaultIconRegistry }
252
+ },
253
+ setup(props, { attrs }) {
254
+ const themeCtx = vue.inject(IrisThemeKey, void 0);
255
+ return () => {
256
+ const icon = icons.resolveThemedIcon(props.registry, props.name, themeCtx?.current.value);
257
+ if (!icon) return null;
258
+ const paint = props.fill ? { fill: "currentColor" } : {
259
+ fill: "none",
260
+ stroke: "currentColor",
261
+ "stroke-width": props.strokeWidth,
262
+ "stroke-linecap": "round",
263
+ "stroke-linejoin": "round"
264
+ };
265
+ const children = icon.nodes.map((node, i) => vue.h(node.tag, { key: i, ...node.attrs }));
266
+ if (props.title) children.unshift(vue.h("title", props.title));
267
+ return vue.h(
268
+ "svg",
269
+ {
270
+ ...attrs,
271
+ xmlns: "http://www.w3.org/2000/svg",
272
+ viewBox: icon.viewBox ?? "0 0 24 24",
273
+ width: props.size,
274
+ height: props.size,
275
+ ...paint,
276
+ role: props.title ? "img" : void 0,
277
+ "aria-label": props.title || void 0,
278
+ "aria-hidden": props.title ? void 0 : "true",
279
+ "data-iris-icon": props.name,
280
+ style: {
281
+ display: "inline-block",
282
+ verticalAlign: "middle",
283
+ flexShrink: 0,
284
+ ...attrs.style ?? {}
285
+ }
286
+ },
287
+ children
288
+ );
289
+ };
290
+ }
291
+ });
292
+
293
+ // src/i18n/context.ts
294
+ var I18nInjectionKey = /* @__PURE__ */ Symbol("IrisI18n");
295
+ var fallbackI18n = null;
296
+ function getFallback() {
297
+ if (fallbackI18n === null) fallbackI18n = core.createI18n();
298
+ return fallbackI18n;
299
+ }
300
+ function useI18n() {
301
+ const i18n = vue.inject(I18nInjectionKey, null) ?? getFallback();
302
+ const state = vue.ref(i18n.getState());
303
+ const unsubscribe = i18n.subscribe((next) => {
304
+ state.value = next;
305
+ });
306
+ vue.onBeforeUnmount(unsubscribe);
307
+ return {
308
+ locale: vue.computed(() => state.value.locale),
309
+ t: (key, params) => {
310
+ void state.value.locale;
311
+ return i18n.t(key, params);
312
+ },
313
+ formatDate: (value, options) => {
314
+ void state.value.locale;
315
+ return i18n.formatDate(value, options);
316
+ },
317
+ formatNumber: (value, options) => {
318
+ void state.value.locale;
319
+ return i18n.formatNumber(value, options);
320
+ },
321
+ formatRelativeTime: (value, unit, options) => {
322
+ void state.value.locale;
323
+ return i18n.formatRelativeTime(value, unit, options);
324
+ },
325
+ setLocale: i18n.setLocale,
326
+ setMessages: i18n.setMessages
327
+ };
328
+ }
329
+
330
+ // src/admin/NavMenu.ts
331
+ var IrisNavMenu = vue.defineComponent({
332
+ name: "IrisNavMenu",
333
+ inheritAttrs: false,
334
+ props: {
335
+ /** Normalized nav tree. */
336
+ items: { type: Array, required: true },
337
+ /** Key of the active leaf. */
338
+ activeKey: { type: String, default: void 0 },
339
+ /** Controlled expanded branch keys (v-model:expandedKeys). */
340
+ expandedKeys: { type: Array, default: void 0 },
341
+ /** Initial expanded keys when uncontrolled (else the active trail expands). */
342
+ defaultExpandedKeys: { type: Array, default: void 0 },
343
+ /** Icon-only rail (top-level items only). */
344
+ collapsed: { type: Boolean, default: false },
345
+ ariaLabel: { type: String, default: void 0 }
346
+ },
347
+ emits: {
348
+ select: (_key, _node) => true,
349
+ "update:expandedKeys": (_keys) => true
350
+ },
351
+ setup(props, { emit, attrs }) {
352
+ const { t } = useI18n();
353
+ const tree = vue.computed(() => core.visibleNav(props.items));
354
+ const activePath = vue.computed(
355
+ () => props.activeKey ? core.findNavPath(props.items, props.activeKey).map((n) => n.key) : []
356
+ );
357
+ const isControlled = vue.computed(() => props.expandedKeys !== void 0);
358
+ const model = core.createExpansion({
359
+ mode: "multiple",
360
+ defaultExpanded: props.defaultExpandedKeys ?? (props.activeKey ? core.branchTrail(props.items, props.activeKey) : [])
361
+ });
362
+ const internalExpanded = vue.shallowRef(model.get());
363
+ vue.onBeforeUnmount(
364
+ model.store.subscribe((keys) => {
365
+ internalExpanded.value = keys;
366
+ })
367
+ );
368
+ const expanded = vue.computed(
369
+ () => isControlled.value ? props.expandedKeys : internalExpanded.value
370
+ );
371
+ vue.watch(
372
+ () => props.activeKey,
373
+ (key) => {
374
+ if (!isControlled.value) model.merge(key ? core.branchTrail(props.items, key) : []);
375
+ }
376
+ );
377
+ const toggle = (key) => {
378
+ if (isControlled.value) {
379
+ const cur = props.expandedKeys;
380
+ emit(
381
+ "update:expandedKeys",
382
+ cur.includes(key) ? cur.filter((k) => k !== key) : [...cur, key]
383
+ );
384
+ } else {
385
+ model.toggle(key);
386
+ emit("update:expandedKeys", model.get());
387
+ }
388
+ };
389
+ const select = (node) => {
390
+ if (node.disabled) return;
391
+ emit("select", node.key, node);
392
+ };
393
+ const hovered = vue.ref(null);
394
+ const itemStyle = (opts) => {
395
+ const bg = opts.active ? "var(--iris-primary)" : hovered.value && !opts.disabled ? "var(--iris-surface)" : "transparent";
396
+ return {
397
+ display: "flex",
398
+ alignItems: "center",
399
+ gap: "10px",
400
+ width: "100%",
401
+ boxSizing: "border-box",
402
+ border: "none",
403
+ borderRadius: "var(--iris-radius-md, 6px)",
404
+ background: bg,
405
+ color: opts.active ? "var(--iris-primary-foreground, #fff)" : opts.trail ? "var(--iris-primary)" : "var(--iris-foreground)",
406
+ font: "inherit",
407
+ fontSize: "14px",
408
+ fontWeight: opts.active || opts.trail ? "600" : "400",
409
+ textAlign: "start",
410
+ cursor: opts.disabled ? "not-allowed" : "pointer",
411
+ opacity: opts.disabled ? "0.5" : "1",
412
+ padding: props.collapsed ? "10px" : `8px 10px 8px ${12 + opts.depth * 16}px`,
413
+ justifyContent: props.collapsed ? "center" : "flex-start"
414
+ };
415
+ };
416
+ const hoverHandlers = (key) => ({
417
+ onMouseenter: () => hovered.value = key,
418
+ onMouseleave: () => {
419
+ if (hovered.value === key) hovered.value = null;
420
+ }
421
+ });
422
+ const iconNode = (node, size = 18) => node.icon ? vue.h(IrisIcon, { name: node.icon, size }) : null;
423
+ const badgeNode = (node) => node.badge !== void 0 && node.badge !== "" && !props.collapsed ? vue.h(
424
+ "span",
425
+ {
426
+ "data-iris-nav-badge": "",
427
+ style: {
428
+ marginInlineStart: "auto",
429
+ fontSize: "11px",
430
+ lineHeight: "1",
431
+ padding: "2px 6px",
432
+ borderRadius: "999px",
433
+ background: "var(--iris-danger, #e5484d)",
434
+ color: "#fff"
435
+ }
436
+ },
437
+ String(node.badge)
438
+ ) : null;
439
+ const renderCollapsed = () => tree.value.map((node) => {
440
+ const branch = core.isBranch(node);
441
+ const active = branch ? activePath.value.includes(node.key) : node.key === props.activeKey;
442
+ return vue.h(
443
+ "button",
444
+ {
445
+ key: node.key,
446
+ type: "button",
447
+ "data-iris-nav-item": "",
448
+ "data-key": node.key,
449
+ "data-active": active ? "true" : void 0,
450
+ "data-branch": branch ? "true" : void 0,
451
+ disabled: node.disabled,
452
+ title: node.title,
453
+ "aria-label": branch ? `${node.title} (section)` : node.title,
454
+ "aria-current": active && !branch ? "page" : void 0,
455
+ style: itemStyle({ depth: 0, active, trail: false, disabled: !!node.disabled }),
456
+ ...hoverHandlers(node.key),
457
+ onClick: () => select(branch ? core.firstLeaf(node) : node)
458
+ },
459
+ [iconNode(node, 20) ?? vue.h("span", { style: { fontWeight: "700" } }, node.title.charAt(0))]
460
+ );
461
+ });
462
+ const renderItem = (node, depth) => {
463
+ const branch = core.isBranch(node);
464
+ const open = expanded.value.includes(node.key);
465
+ const active = node.key === props.activeKey;
466
+ const trail = branch && activePath.value.includes(node.key);
467
+ const row = vue.h(
468
+ "button",
469
+ {
470
+ type: "button",
471
+ "data-iris-nav-item": "",
472
+ "data-key": node.key,
473
+ "data-branch": branch ? "true" : void 0,
474
+ "data-active": active ? "true" : void 0,
475
+ "data-open": branch && open ? "true" : void 0,
476
+ "data-depth": String(depth),
477
+ disabled: node.disabled,
478
+ "aria-expanded": branch ? open ? "true" : "false" : void 0,
479
+ "aria-current": active ? "page" : void 0,
480
+ style: itemStyle({ depth, active, trail, disabled: !!node.disabled }),
481
+ ...hoverHandlers(node.key),
482
+ onClick: () => branch ? toggle(node.key) : select(node)
483
+ },
484
+ [
485
+ iconNode(node),
486
+ vue.h(
487
+ "span",
488
+ {
489
+ style: {
490
+ flex: branch ? "1" : "0 1 auto",
491
+ overflow: "hidden",
492
+ textOverflow: "ellipsis",
493
+ whiteSpace: "nowrap"
494
+ }
495
+ },
496
+ node.title
497
+ ),
498
+ badgeNode(node),
499
+ branch ? vue.h(IrisIcon, {
500
+ name: open ? "chevron-down" : "chevron-right",
501
+ size: 16,
502
+ style: { marginInlineStart: badgeNode(node) ? "6px" : "auto", opacity: "0.6" }
503
+ }) : null
504
+ ]
505
+ );
506
+ if (!branch || !open)
507
+ return vue.h("div", { key: node.key, "data-iris-nav-group": branch ? "" : void 0 }, [row]);
508
+ return vue.h("div", { key: node.key, "data-iris-nav-group": "", "data-open": "true" }, [
509
+ row,
510
+ vue.h(
511
+ "div",
512
+ { "data-iris-nav-children": "", role: "group" },
513
+ (node.children ?? []).map((child) => renderItem(child, depth + 1))
514
+ )
515
+ ]);
516
+ };
517
+ const onKeydown = (e) => {
518
+ const root = e.currentTarget;
519
+ const buttons = Array.from(root.querySelectorAll("[data-iris-nav-item]"));
520
+ if (buttons.length === 0) return;
521
+ const idx = buttons.indexOf(document.activeElement);
522
+ const focusAt = (i) => buttons[(i + buttons.length) % buttons.length]?.focus();
523
+ switch (e.key) {
524
+ case "ArrowDown":
525
+ e.preventDefault();
526
+ focusAt(idx < 0 ? 0 : idx + 1);
527
+ return;
528
+ case "ArrowUp":
529
+ e.preventDefault();
530
+ focusAt(idx < 0 ? buttons.length - 1 : idx - 1);
531
+ return;
532
+ case "Home":
533
+ e.preventDefault();
534
+ buttons[0]?.focus();
535
+ return;
536
+ case "End":
537
+ e.preventDefault();
538
+ buttons[buttons.length - 1]?.focus();
539
+ return;
540
+ }
541
+ if (props.collapsed || idx < 0) return;
542
+ const key = buttons[idx]?.getAttribute("data-key");
543
+ if (!key) return;
544
+ const node = core.findNavNode(props.items, key);
545
+ if (e.key === "ArrowRight" && node && core.isBranch(node)) {
546
+ e.preventDefault();
547
+ if (!expanded.value.includes(key)) toggle(key);
548
+ else focusAt(idx + 1);
549
+ } else if (e.key === "ArrowLeft") {
550
+ if (node && core.isBranch(node) && expanded.value.includes(key)) {
551
+ e.preventDefault();
552
+ toggle(key);
553
+ } else {
554
+ const parentKey = core.findNavPath(props.items, key).at(-2)?.key;
555
+ if (parentKey) {
556
+ e.preventDefault();
557
+ buttons.find((b) => b.getAttribute("data-key") === parentKey)?.focus();
558
+ }
559
+ }
560
+ }
561
+ };
562
+ return () => vue.h(
563
+ "nav",
564
+ {
565
+ ...attrs,
566
+ "data-iris-nav-menu": "",
567
+ "data-collapsed": props.collapsed ? "true" : void 0,
568
+ "aria-label": props.ariaLabel ?? t("admin.nav"),
569
+ onKeydown,
570
+ style: {
571
+ display: "flex",
572
+ flexDirection: "column",
573
+ gap: "2px",
574
+ ...attrs.style ?? {}
575
+ }
576
+ },
577
+ props.collapsed ? renderCollapsed() : tree.value.map((node) => renderItem(node, 0))
578
+ );
579
+ }
580
+ });
581
+ var IrisBreadcrumb = vue.defineComponent({
582
+ name: "IrisBreadcrumb",
583
+ inheritAttrs: false,
584
+ props: {
585
+ /** String separator. Ignored if the `separator` slot is provided. */
586
+ separator: { type: String, default: "/" }
587
+ },
588
+ setup(props, { slots, attrs }) {
589
+ const { t } = useI18n();
590
+ return () => {
591
+ const children = slots.default?.() ?? [];
592
+ const flat = [];
593
+ const walk = (nodes) => {
594
+ if (!nodes) return;
595
+ nodes.forEach((n) => {
596
+ if (n.type === vue.Fragment) walk(n.children);
597
+ else flat.push(n);
598
+ });
599
+ };
600
+ walk(children);
601
+ const total = flat.length;
602
+ const slotsSep = slots.separator;
603
+ const sep = (key) => vue.h(
604
+ "li",
605
+ {
606
+ key: `sep-${key}`,
607
+ "data-iris-breadcrumb-separator": "",
608
+ "aria-hidden": "true",
609
+ style: { color: "var(--iris-muted)", display: "inline-flex", alignItems: "center" }
610
+ },
611
+ slotsSep ? slotsSep() : props.separator
612
+ );
613
+ const items = [];
614
+ flat.forEach((child, i) => {
615
+ const isLast = i === total - 1;
616
+ const wrapped = vue.h(
617
+ "li",
618
+ {
619
+ key: `item-${i}`,
620
+ "data-iris-breadcrumb-item": "",
621
+ "data-iris-breadcrumb-last": isLast ? "true" : void 0,
622
+ style: { display: "inline-flex", alignItems: "center" }
623
+ },
624
+ // Pass isCurrent=true to the last child so it self-marks aria-current.
625
+ vue.h(
626
+ child.type,
627
+ { ...child.props ?? {}, isCurrent: isLast },
628
+ child.children
629
+ )
630
+ );
631
+ items.push(wrapped);
632
+ if (!isLast) items.push(sep(i));
633
+ });
634
+ return vue.h(
635
+ "nav",
636
+ {
637
+ ...attrs,
638
+ "aria-label": t("breadcrumb.label"),
639
+ "data-iris-breadcrumb": ""
640
+ },
641
+ vue.h(
642
+ "ol",
643
+ {
644
+ style: {
645
+ display: "inline-flex",
646
+ alignItems: "center",
647
+ gap: "6px",
648
+ margin: "0",
649
+ padding: "0",
650
+ listStyle: "none",
651
+ fontSize: "14px"
652
+ }
653
+ },
654
+ items
655
+ )
656
+ );
657
+ };
658
+ }
659
+ });
660
+ var IrisBreadcrumbItem = vue.defineComponent({
661
+ name: "IrisBreadcrumbItem",
662
+ inheritAttrs: false,
663
+ props: {
664
+ href: { type: String, default: "" },
665
+ /** Internal: set by parent IrisBreadcrumb when this is the last item. */
666
+ isCurrent: { type: Boolean, default: false }
667
+ },
668
+ setup(props, { slots, attrs }) {
669
+ return () => {
670
+ const last = props.isCurrent;
671
+ const baseStyle = {
672
+ color: last ? "var(--iris-muted)" : "var(--iris-primary)",
673
+ textDecoration: "none",
674
+ cursor: last ? "default" : props.href ? "pointer" : "default"
675
+ };
676
+ const baseAttrs = {
677
+ ...attrs,
678
+ "aria-current": last ? "page" : void 0,
679
+ "data-iris-breadcrumb-crumb": "",
680
+ style: { ...baseStyle, ...attrs.style ?? {} }
681
+ };
682
+ if (props.href && !last) {
683
+ return vue.h("a", { ...baseAttrs, href: props.href }, slots.default?.());
684
+ }
685
+ return vue.h("span", baseAttrs, slots.default?.());
686
+ };
687
+ }
688
+ });
689
+
690
+ // src/admin/AdminBreadcrumb.ts
691
+ var IrisAdminBreadcrumb = vue.defineComponent({
692
+ name: "IrisAdminBreadcrumb",
693
+ inheritAttrs: false,
694
+ props: {
695
+ /** Root→current ancestor chain. */
696
+ trail: { type: Array, required: true },
697
+ /** Show each node's icon before its title. */
698
+ showIcon: { type: Boolean, default: true },
699
+ /** Hide the breadcrumb when the trail has a single crumb. */
700
+ hideSingle: { type: Boolean, default: false },
701
+ separator: { type: String, default: "/" }
702
+ },
703
+ emits: {
704
+ select: (_key, _node) => true
705
+ },
706
+ setup(props, { emit, attrs }) {
707
+ return () => {
708
+ const trail = props.trail;
709
+ if (trail.length === 0 || props.hideSingle && trail.length === 1) return null;
710
+ return vue.h(
711
+ IrisBreadcrumb,
712
+ { ...attrs, separator: props.separator },
713
+ {
714
+ default: () => trail.map((node, i) => {
715
+ const last = i === trail.length - 1;
716
+ const content = [];
717
+ if (props.showIcon && node.icon) {
718
+ content.push(
719
+ vue.h(IrisIcon, { name: node.icon, size: 14, style: { marginInlineEnd: "4px" } })
720
+ );
721
+ }
722
+ content.push(vue.h("span", node.title));
723
+ return vue.h(
724
+ IrisBreadcrumbItem,
725
+ {
726
+ key: node.key,
727
+ "data-iris-admin-crumb": "",
728
+ style: {
729
+ display: "inline-flex",
730
+ alignItems: "center",
731
+ cursor: last ? "default" : "pointer"
732
+ },
733
+ onClick: last ? void 0 : () => emit("select", node.key, node)
734
+ },
735
+ { default: () => content }
736
+ );
737
+ })
738
+ }
739
+ );
740
+ };
741
+ }
742
+ });
743
+ function useTabsNav(nav) {
744
+ const state = vue.ref(nav.getState());
745
+ const unsubscribe = nav.subscribe((next) => {
746
+ state.value = next;
747
+ });
748
+ vue.onBeforeUnmount(unsubscribe);
749
+ return {
750
+ tabs: vue.computed(() => state.value.tabs),
751
+ activeKey: vue.computed(() => state.value.activeKey),
752
+ cacheKeys: vue.computed(() => state.value.tabs.map((t) => nav.cacheKey(t.key))),
753
+ open: nav.open,
754
+ activate: nav.activate,
755
+ close: nav.close,
756
+ closeOthers: nav.closeOthers,
757
+ closeAll: nav.closeAll,
758
+ closeLeft: nav.closeLeft,
759
+ closeRight: nav.closeRight,
760
+ refresh: nav.refresh,
761
+ setPinned: nav.setPinned,
762
+ cacheKey: nav.cacheKey
763
+ };
764
+ }
765
+ function useMachine(machine) {
766
+ const state = vue.ref(machine.store.getState());
767
+ const unsubscribe = machine.store.subscribe((next) => {
768
+ state.value = next;
769
+ });
770
+ vue.onBeforeUnmount(() => {
771
+ unsubscribe();
772
+ });
773
+ return { state, send: machine.send };
774
+ }
775
+
776
+ // src/primitives/dropdown/context.ts
777
+ var DropdownContextKey = /* @__PURE__ */ Symbol("IrisDropdown");
778
+
779
+ // src/primitives/dropdown/Dropdown.ts
780
+ var IrisDropdown = vue.defineComponent({
781
+ name: "IrisDropdown",
782
+ props: {
783
+ open: { type: Boolean, default: void 0 },
784
+ defaultOpen: { type: Boolean, default: false },
785
+ placement: { type: String, default: "bottom-start" },
786
+ offset: { type: Number, default: 6 }
787
+ },
788
+ emits: {
789
+ "update:open": (_value) => true
790
+ },
791
+ setup(props, { slots, emit }) {
792
+ const isControlled = vue.computed(() => props.open !== void 0);
793
+ const machine = core.createFloatingMachine(props.defaultOpen ? "open" : "closed");
794
+ const { state, send } = useMachine(machine);
795
+ vue.watch(
796
+ () => props.open,
797
+ (value) => {
798
+ if (value === void 0) return;
799
+ const currentlyOpen = state.value.value === "open";
800
+ if (value && !currentlyOpen) send({ type: "OPEN" });
801
+ if (!value && currentlyOpen) send({ type: "CLOSE" });
802
+ },
803
+ { immediate: true }
804
+ );
805
+ const open = vue.computed(
806
+ () => isControlled.value ? Boolean(props.open) : state.value.value === "open"
807
+ );
808
+ const setOpen = (value) => {
809
+ if (!isControlled.value) send({ type: value ? "OPEN" : "CLOSE" });
810
+ emit("update:open", value);
811
+ };
812
+ vue.provide(DropdownContextKey, {
813
+ open,
814
+ setOpen,
815
+ triggerRef: vue.ref(null),
816
+ contentRef: vue.ref(null),
817
+ contentId: vue.useId(),
818
+ placement: props.placement,
819
+ offset: props.offset
820
+ });
821
+ return () => slots.default?.();
822
+ }
823
+ });
824
+ function isEventListenerKey(key) {
825
+ return key.startsWith("on") && key.length > 2 && /[A-Z]/.test(key.charAt(2));
826
+ }
827
+ function composeRefs(...refs) {
828
+ return (el) => {
829
+ for (const r of refs) {
830
+ if (r == null) continue;
831
+ if (typeof r === "function") {
832
+ r(el);
833
+ } else if (typeof r === "object" && "value" in r) {
834
+ r.value = el;
835
+ }
836
+ }
837
+ };
838
+ }
839
+ function mergeSlotProps(parent, child) {
840
+ const out = { ...child };
841
+ for (const key in parent) {
842
+ const parentVal = parent[key];
843
+ const childVal = child[key];
844
+ if (isEventListenerKey(key)) {
845
+ if (typeof parentVal === "function" && typeof childVal === "function") {
846
+ out[key] = core.composeEventHandlers(
847
+ parentVal,
848
+ childVal
849
+ );
850
+ } else if (typeof parentVal === "function" && childVal == null) {
851
+ out[key] = parentVal;
852
+ }
853
+ } else if (key === "ref") {
854
+ if (parentVal != null && childVal != null) {
855
+ out[key] = composeRefs(parentVal, childVal);
856
+ } else if (parentVal != null) {
857
+ out[key] = parentVal;
858
+ }
859
+ } else if (key === "class") {
860
+ const merged = [parentVal, childVal].filter(Boolean).join(" ").trim();
861
+ if (merged) out[key] = merged;
862
+ } else if (key === "style") {
863
+ if (parentVal || childVal) {
864
+ out[key] = { ...parentVal, ...childVal };
865
+ }
866
+ } else if (!(key in child)) {
867
+ out[key] = parentVal;
868
+ }
869
+ }
870
+ return out;
871
+ }
872
+ vue.defineComponent({
873
+ name: "IrisSlot",
874
+ inheritAttrs: false,
875
+ setup(_, { slots, attrs }) {
876
+ return () => {
877
+ const children = slots.default?.();
878
+ const root = findFirstElement(children);
879
+ if (!root) {
880
+ if (typeof process === "undefined" || process.env?.NODE_ENV !== "production") {
881
+ console.warn("[iris-ui] IrisSlot expected exactly one child element; got none.");
882
+ }
883
+ return null;
884
+ }
885
+ const merged = mergeSlotProps(
886
+ attrs,
887
+ root.props ?? {}
888
+ );
889
+ return vue.h(root.type, merged, root.children);
890
+ };
891
+ }
892
+ });
893
+ function findFirstElement(children) {
894
+ if (!children || children.length === 0) return null;
895
+ let found = null;
896
+ let extraCount = 0;
897
+ const walk = (nodes) => {
898
+ for (const node of nodes) {
899
+ if (node.type === vue.Fragment) {
900
+ const fragChildren = Array.isArray(node.children) ? node.children : [];
901
+ walk(fragChildren);
902
+ continue;
903
+ }
904
+ if (typeof node.type === "string" || typeof node.type === "object" || typeof node.type === "function") {
905
+ if (found) {
906
+ extraCount += 1;
907
+ } else {
908
+ found = node;
909
+ }
910
+ }
911
+ }
912
+ };
913
+ walk(children);
914
+ if (extraCount > 0 && (typeof process === "undefined" || process.env?.NODE_ENV !== "production")) {
915
+ console.warn(`[iris-ui] IrisSlot got ${extraCount + 1} root elements; only the first is used.`);
916
+ }
917
+ return found;
918
+ }
919
+
920
+ // src/primitives/dropdown/DropdownTrigger.ts
921
+ var IrisDropdownTrigger = vue.defineComponent({
922
+ name: "IrisDropdownTrigger",
923
+ inheritAttrs: false,
924
+ props: {
925
+ asChild: { type: Boolean, default: false }
926
+ },
927
+ setup(props, { slots, attrs }) {
928
+ const ctx = vue.inject(DropdownContextKey);
929
+ if (!ctx) {
930
+ throw new Error("[iris-ui] IrisDropdownTrigger must be a descendant of IrisDropdown");
931
+ }
932
+ const onClick = (event) => {
933
+ if (event.defaultPrevented) return;
934
+ ctx.setOpen(!ctx.open.value);
935
+ };
936
+ const onKeyDown = (event) => {
937
+ if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
938
+ event.preventDefault();
939
+ ctx.setOpen(true);
940
+ }
941
+ };
942
+ const captureRef = (el) => {
943
+ ctx.triggerRef.value = el ?? null;
944
+ };
945
+ return () => {
946
+ const triggerProps = {
947
+ "aria-haspopup": "menu",
948
+ "aria-expanded": ctx.open.value ? "true" : "false",
949
+ "aria-controls": ctx.contentId,
950
+ "data-state": ctx.open.value ? "open" : "closed",
951
+ onClick,
952
+ onKeydown: onKeyDown,
953
+ ref: captureRef
954
+ };
955
+ if (props.asChild) {
956
+ const root = findFirstElement(slots.default?.());
957
+ if (!root) {
958
+ if (typeof process === "undefined" || process.env?.NODE_ENV !== "production") {
959
+ console.warn("[iris-ui] IrisDropdownTrigger: as-child requires a single child element");
960
+ }
961
+ return null;
962
+ }
963
+ const merged = mergeSlotProps(
964
+ { ...triggerProps, ...attrs },
965
+ root.props ?? {}
966
+ );
967
+ return vue.h(root.type, merged, root.children);
968
+ }
969
+ return vue.h("button", { type: "button", ...attrs, ...triggerProps }, slots.default?.());
970
+ };
971
+ }
972
+ });
973
+ function useFloating(options) {
974
+ const x = vue.ref(0);
975
+ const y = vue.ref(0);
976
+ const strategy = vue.ref(options.strategy ?? "absolute");
977
+ const finalPlacement = vue.ref(options.placement ?? "bottom");
978
+ const arrowX = vue.ref();
979
+ const arrowY = vue.ref();
980
+ const middleware = vue.shallowRef([]);
981
+ rebuildMiddleware();
982
+ function rebuildMiddleware() {
983
+ const mw = [];
984
+ if (options.offset !== void 0) mw.push(dom.offset(options.offset));
985
+ if (options.flip !== false) mw.push(dom.flip());
986
+ if (options.shift !== false) mw.push(dom.shift({ padding: 8 }));
987
+ if (options.size) {
988
+ const padding = typeof options.size === "number" ? options.size : 8;
989
+ mw.push(
990
+ dom.size({
991
+ padding,
992
+ apply({ availableWidth, availableHeight, elements }) {
993
+ Object.assign(elements.floating.style, {
994
+ maxWidth: `${Math.max(0, Math.floor(availableWidth))}px`,
995
+ maxHeight: `${Math.max(0, Math.floor(availableHeight))}px`
996
+ });
997
+ }
998
+ })
999
+ );
1000
+ }
1001
+ if (options.arrow?.value) mw.push(dom.arrow({ element: options.arrow.value }));
1002
+ if (options.middleware) mw.push(...options.middleware);
1003
+ middleware.value = mw;
1004
+ }
1005
+ let cleanupAutoUpdate = null;
1006
+ let epoch = 0;
1007
+ async function update() {
1008
+ const a = options.anchor.value;
1009
+ const f = options.floating.value;
1010
+ if (!a || !f) return;
1011
+ rebuildMiddleware();
1012
+ const token = ++epoch;
1013
+ const result = await dom.computePosition(a, f, {
1014
+ placement: options.placement ?? "bottom",
1015
+ strategy: strategy.value,
1016
+ middleware: middleware.value
1017
+ });
1018
+ if (token !== epoch) return;
1019
+ x.value = result.x;
1020
+ y.value = result.y;
1021
+ finalPlacement.value = result.placement;
1022
+ const arrowData = result.middlewareData.arrow;
1023
+ arrowX.value = arrowData?.x;
1024
+ arrowY.value = arrowData?.y;
1025
+ }
1026
+ vue.watch(
1027
+ [options.open, options.anchor, options.floating],
1028
+ ([isOpen, anchorEl, floatingEl]) => {
1029
+ cleanupAutoUpdate?.();
1030
+ cleanupAutoUpdate = null;
1031
+ epoch++;
1032
+ if (isOpen && anchorEl && floatingEl) {
1033
+ cleanupAutoUpdate = dom.autoUpdate(anchorEl, floatingEl, () => {
1034
+ void update();
1035
+ });
1036
+ }
1037
+ },
1038
+ { flush: "post", immediate: true }
1039
+ );
1040
+ vue.onScopeDispose(() => {
1041
+ cleanupAutoUpdate?.();
1042
+ cleanupAutoUpdate = null;
1043
+ epoch++;
1044
+ });
1045
+ const OPPOSITE = {
1046
+ top: "bottom",
1047
+ bottom: "top",
1048
+ left: "right",
1049
+ right: "left"
1050
+ };
1051
+ const arrowSide = vue.computed(
1052
+ () => OPPOSITE[finalPlacement.value.split("-")[0]]
1053
+ );
1054
+ const floatingStyles = vue.computed(() => ({
1055
+ position: strategy.value,
1056
+ top: "0",
1057
+ left: "0",
1058
+ transform: `translate3d(${Math.round(x.value)}px, ${Math.round(y.value)}px, 0)`,
1059
+ width: "max-content"
1060
+ }));
1061
+ return { x, y, strategy, finalPlacement, floatingStyles, update, arrowX, arrowY, arrowSide };
1062
+ }
1063
+ function useDismiss(options) {
1064
+ const escape = options.escape !== false;
1065
+ const outsidePointerDown = options.outsidePointerDown !== false;
1066
+ const isExcluded = (target) => {
1067
+ if (!target || !(target instanceof Node)) return false;
1068
+ for (const r of options.exclude) {
1069
+ const el = r.value;
1070
+ if (!el) continue;
1071
+ if (el === target || el.contains(target)) return true;
1072
+ }
1073
+ return false;
1074
+ };
1075
+ const onPointerDown = (event) => {
1076
+ if (!options.enabled.value) return;
1077
+ if (isExcluded(event.target)) return;
1078
+ options.onDismiss();
1079
+ };
1080
+ const onKeyDown = (event) => {
1081
+ if (!options.enabled.value) return;
1082
+ if (event.key === "Escape") {
1083
+ event.stopPropagation();
1084
+ options.onDismiss();
1085
+ }
1086
+ };
1087
+ const attach = () => {
1088
+ if (typeof document === "undefined") return;
1089
+ if (outsidePointerDown) {
1090
+ document.addEventListener("pointerdown", onPointerDown, true);
1091
+ }
1092
+ if (escape) {
1093
+ document.addEventListener("keydown", onKeyDown);
1094
+ }
1095
+ };
1096
+ const detach = () => {
1097
+ if (typeof document === "undefined") return;
1098
+ document.removeEventListener("pointerdown", onPointerDown, true);
1099
+ document.removeEventListener("keydown", onKeyDown);
1100
+ };
1101
+ vue.watch(
1102
+ options.enabled,
1103
+ (enabled) => {
1104
+ detach();
1105
+ if (enabled) attach();
1106
+ },
1107
+ { immediate: true, flush: "post" }
1108
+ );
1109
+ vue.onScopeDispose(detach);
1110
+ }
1111
+
1112
+ // src/primitives/dropdown/DropdownMenu.ts
1113
+ var IrisDropdownMenu = vue.defineComponent({
1114
+ name: "IrisDropdownMenu",
1115
+ inheritAttrs: false,
1116
+ props: {
1117
+ teleport: {
1118
+ type: [String, Object, Boolean],
1119
+ default: "body"
1120
+ }
1121
+ },
1122
+ setup(props, { slots, attrs }) {
1123
+ const ctx = vue.inject(DropdownContextKey);
1124
+ if (!ctx) {
1125
+ throw new Error("[iris-ui] IrisDropdownMenu must be a descendant of IrisDropdown");
1126
+ }
1127
+ const innerRef = vue.ref(null);
1128
+ vue.watch(innerRef, (el) => {
1129
+ ctx.contentRef.value = el;
1130
+ });
1131
+ const { floatingStyles } = useFloating({
1132
+ anchor: ctx.triggerRef,
1133
+ floating: innerRef,
1134
+ open: ctx.open,
1135
+ placement: ctx.placement,
1136
+ offset: ctx.offset
1137
+ });
1138
+ useDismiss({
1139
+ enabled: ctx.open,
1140
+ exclude: [ctx.triggerRef, innerRef],
1141
+ onDismiss: () => ctx.setOpen(false)
1142
+ });
1143
+ vue.watch(ctx.open, async (open) => {
1144
+ if (open) {
1145
+ await vue.nextTick();
1146
+ const items = innerRef.value?.querySelectorAll('[role="menuitem"]');
1147
+ items?.[0]?.focus();
1148
+ } else {
1149
+ ctx.triggerRef.value?.focus?.();
1150
+ }
1151
+ });
1152
+ const typeahead = {
1153
+ buffer: "",
1154
+ timer: null
1155
+ };
1156
+ const onKeyDown = (event) => {
1157
+ if (!ctx.open.value) return;
1158
+ const items = Array.from(
1159
+ innerRef.value?.querySelectorAll(
1160
+ '[role="menuitem"]:not([aria-disabled="true"])'
1161
+ ) ?? []
1162
+ );
1163
+ if (items.length === 0) return;
1164
+ const current = document.activeElement;
1165
+ const index = current ? items.indexOf(current) : -1;
1166
+ switch (event.key) {
1167
+ case "ArrowDown": {
1168
+ event.preventDefault();
1169
+ items[core.nextEnabledIndex(index, 1, items.length)]?.focus();
1170
+ break;
1171
+ }
1172
+ case "ArrowUp": {
1173
+ event.preventDefault();
1174
+ items[core.nextEnabledIndex(index, -1, items.length)]?.focus();
1175
+ break;
1176
+ }
1177
+ case "Home":
1178
+ event.preventDefault();
1179
+ items[0]?.focus();
1180
+ break;
1181
+ case "End":
1182
+ event.preventDefault();
1183
+ items[items.length - 1]?.focus();
1184
+ break;
1185
+ case "Tab":
1186
+ ctx.setOpen(false);
1187
+ break;
1188
+ default: {
1189
+ if (event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {
1190
+ typeahead.buffer += event.key;
1191
+ if (typeahead.timer) clearTimeout(typeahead.timer);
1192
+ typeahead.timer = setTimeout(() => {
1193
+ typeahead.buffer = "";
1194
+ }, 500);
1195
+ const match = core.matchTypeahead(
1196
+ items.map((it) => it.textContent ?? ""),
1197
+ typeahead.buffer,
1198
+ index
1199
+ );
1200
+ if (match >= 0) {
1201
+ event.preventDefault();
1202
+ items[match]?.focus();
1203
+ }
1204
+ }
1205
+ }
1206
+ }
1207
+ };
1208
+ return () => {
1209
+ if (!ctx.open.value) return null;
1210
+ const menuNode = vue.h(
1211
+ "div",
1212
+ {
1213
+ ...attrs,
1214
+ ref: (el) => {
1215
+ innerRef.value = el ?? null;
1216
+ },
1217
+ id: ctx.contentId,
1218
+ role: "menu",
1219
+ tabindex: -1,
1220
+ "data-iris-dropdown-menu": "",
1221
+ "data-state": "open",
1222
+ onKeydown: onKeyDown,
1223
+ style: {
1224
+ ...floatingStyles.value,
1225
+ background: "var(--iris-surface)",
1226
+ color: "var(--iris-foreground)",
1227
+ border: "1px solid var(--iris-border)",
1228
+ borderRadius: "var(--iris-radius-md)",
1229
+ padding: "var(--iris-padding-sm)",
1230
+ boxShadow: "0 8px 24px -8px rgba(0, 0, 0, 0.16), 0 4px 8px -2px rgba(0, 0, 0, 0.08)",
1231
+ minWidth: "160px",
1232
+ outline: "none",
1233
+ zIndex: "1000",
1234
+ ...attrs.style ?? {}
1235
+ }
1236
+ },
1237
+ slots.default?.()
1238
+ );
1239
+ if (props.teleport === false) return menuNode;
1240
+ return vue.h(vue.Teleport, { to: props.teleport }, [menuNode]);
1241
+ };
1242
+ }
1243
+ });
1244
+ var IrisDropdownItem = vue.defineComponent({
1245
+ name: "IrisDropdownItem",
1246
+ inheritAttrs: false,
1247
+ props: {
1248
+ disabled: { type: Boolean, default: false },
1249
+ /** When true, selecting does NOT close the dropdown. Useful for sub-actions. */
1250
+ keepOpen: { type: Boolean, default: false }
1251
+ },
1252
+ emits: {
1253
+ select: (_event) => true
1254
+ },
1255
+ setup(props, { slots, attrs, emit }) {
1256
+ const ctx = vue.inject(DropdownContextKey);
1257
+ if (!ctx) {
1258
+ throw new Error("[iris-ui] IrisDropdownItem must be a descendant of IrisDropdownMenu");
1259
+ }
1260
+ const hovered = vue.ref(false);
1261
+ const fire = (event) => {
1262
+ if (props.disabled) return;
1263
+ emit("select", event);
1264
+ if (!props.keepOpen) ctx.setOpen(false);
1265
+ };
1266
+ const onClick = (event) => {
1267
+ if (event.defaultPrevented) return;
1268
+ fire(event);
1269
+ };
1270
+ const onKeyDown = (event) => {
1271
+ if (event.key === "Enter" || event.key === " ") {
1272
+ event.preventDefault();
1273
+ fire(event);
1274
+ }
1275
+ };
1276
+ return () => vue.h(
1277
+ "div",
1278
+ {
1279
+ ...attrs,
1280
+ role: "menuitem",
1281
+ tabindex: props.disabled ? -1 : 0,
1282
+ "aria-disabled": props.disabled ? "true" : void 0,
1283
+ "data-iris-dropdown-item": "",
1284
+ "data-disabled": props.disabled ? "" : void 0,
1285
+ onClick,
1286
+ onKeydown: onKeyDown,
1287
+ onPointerenter: () => hovered.value = true,
1288
+ onPointerleave: () => hovered.value = false,
1289
+ onFocus: () => hovered.value = true,
1290
+ onBlur: () => hovered.value = false,
1291
+ style: {
1292
+ display: "flex",
1293
+ alignItems: "center",
1294
+ gap: "var(--iris-gap-sm)",
1295
+ padding: "6px var(--iris-padding-md)",
1296
+ borderRadius: "var(--iris-radius-sm)",
1297
+ cursor: props.disabled ? "not-allowed" : "pointer",
1298
+ opacity: props.disabled ? "0.5" : "1",
1299
+ background: hovered.value && !props.disabled ? "var(--iris-surface-hover)" : "transparent",
1300
+ color: "inherit",
1301
+ outline: "none",
1302
+ fontSize: "14px",
1303
+ transition: "background-color 80ms ease",
1304
+ ...attrs.style ?? {}
1305
+ }
1306
+ },
1307
+ slots.default?.()
1308
+ );
1309
+ }
1310
+ });
1311
+ var IrisDropdownSeparator = vue.defineComponent({
1312
+ name: "IrisDropdownSeparator",
1313
+ inheritAttrs: false,
1314
+ setup(_, { attrs }) {
1315
+ return () => vue.h("div", {
1316
+ ...attrs,
1317
+ role: "separator",
1318
+ "data-iris-dropdown-separator": "",
1319
+ style: {
1320
+ height: "1px",
1321
+ background: "var(--iris-border)",
1322
+ margin: "4px 0",
1323
+ ...attrs.style ?? {}
1324
+ }
1325
+ });
1326
+ }
1327
+ });
1328
+
1329
+ // src/admin/AdminTabs.ts
1330
+ var IrisAdminTabs = vue.defineComponent({
1331
+ name: "IrisAdminTabs",
1332
+ inheritAttrs: false,
1333
+ props: {
1334
+ /** Shared tabs store (from `createTabsNav`). */
1335
+ nav: { type: Object, required: true }
1336
+ },
1337
+ emits: {
1338
+ change: (_key) => true,
1339
+ close: (_key) => true,
1340
+ refresh: (_key) => true
1341
+ },
1342
+ setup(props, { emit, attrs }) {
1343
+ const { t: tr } = useI18n();
1344
+ const t = useTabsNav(props.nav);
1345
+ const activate = (key) => {
1346
+ props.nav.activate(key);
1347
+ emit("change", key);
1348
+ };
1349
+ const close = (key) => {
1350
+ props.nav.close(key);
1351
+ emit("close", key);
1352
+ };
1353
+ const refresh = (key) => {
1354
+ props.nav.refresh(key);
1355
+ emit("refresh", key);
1356
+ };
1357
+ const focusTab = (root, key) => {
1358
+ if (!root || !key) return;
1359
+ const labels = root.querySelectorAll("[data-iris-tab-label]");
1360
+ labels.forEach((el) => {
1361
+ if (el.getAttribute("data-key") === key) el.focus();
1362
+ });
1363
+ };
1364
+ const onTablistKeydown = (e) => {
1365
+ const root = e.currentTarget;
1366
+ const tabs = t.tabs.value;
1367
+ const idx = tabs.findIndex((x) => x.key === t.activeKey.value);
1368
+ if (idx < 0 || tabs.length === 0) return;
1369
+ if (e.key === "Delete" || e.key === "Backspace") {
1370
+ const cur = tabs[idx];
1371
+ if (cur && core.isClosable(cur)) {
1372
+ e.preventDefault();
1373
+ close(cur.key);
1374
+ void vue.nextTick(() => focusTab(root, props.nav.getState().activeKey));
1375
+ }
1376
+ return;
1377
+ }
1378
+ let target = -1;
1379
+ if (e.key === "ArrowRight") target = (idx + 1) % tabs.length;
1380
+ else if (e.key === "ArrowLeft") target = (idx - 1 + tabs.length) % tabs.length;
1381
+ else if (e.key === "Home") target = 0;
1382
+ else if (e.key === "End") target = tabs.length - 1;
1383
+ else return;
1384
+ e.preventDefault();
1385
+ const key = tabs[target]?.key;
1386
+ if (!key) return;
1387
+ activate(key);
1388
+ void vue.nextTick(() => focusTab(root, key));
1389
+ };
1390
+ const chip = (tab) => {
1391
+ const active = tab.key === t.activeKey.value;
1392
+ const closable = core.isClosable(tab);
1393
+ const labelChildren = [];
1394
+ if (tab.icon) labelChildren.push(vue.h(IrisIcon, { name: tab.icon, size: 14 }));
1395
+ labelChildren.push(vue.h("span", { style: { whiteSpace: "nowrap" } }, tab.title));
1396
+ const label = vue.h(
1397
+ "button",
1398
+ {
1399
+ type: "button",
1400
+ role: "tab",
1401
+ "data-iris-tab-label": "",
1402
+ "data-key": tab.key,
1403
+ "aria-selected": active ? "true" : "false",
1404
+ "aria-current": active ? "page" : void 0,
1405
+ tabindex: active ? 0 : -1,
1406
+ style: {
1407
+ display: "inline-flex",
1408
+ alignItems: "center",
1409
+ gap: "6px",
1410
+ border: "none",
1411
+ background: "transparent",
1412
+ color: "inherit",
1413
+ font: "inherit",
1414
+ fontSize: "13px",
1415
+ cursor: "pointer",
1416
+ padding: "0"
1417
+ },
1418
+ onClick: () => activate(tab.key)
1419
+ },
1420
+ labelChildren
1421
+ );
1422
+ const closeBtn = closable ? vue.h(
1423
+ "button",
1424
+ {
1425
+ type: "button",
1426
+ tabindex: -1,
1427
+ "data-iris-tab-close": "",
1428
+ "aria-label": tr("admin.closeTab", { title: tab.title }),
1429
+ style: {
1430
+ display: "inline-flex",
1431
+ alignItems: "center",
1432
+ justifyContent: "center",
1433
+ width: "16px",
1434
+ height: "16px",
1435
+ border: "none",
1436
+ borderRadius: "4px",
1437
+ background: "transparent",
1438
+ color: "inherit",
1439
+ cursor: "pointer",
1440
+ padding: "0",
1441
+ opacity: "0.6"
1442
+ },
1443
+ onClick: (e) => {
1444
+ e.stopPropagation();
1445
+ close(tab.key);
1446
+ }
1447
+ },
1448
+ [vue.h(IrisIcon, { name: "x", size: 12 })]
1449
+ ) : tab.pinned ? vue.h(IrisIcon, { name: "check-circle", size: 12, style: { opacity: "0.5" } }) : null;
1450
+ return vue.h(
1451
+ "div",
1452
+ {
1453
+ key: tab.key,
1454
+ "data-iris-tab": "",
1455
+ "data-active": active ? "true" : void 0,
1456
+ style: {
1457
+ display: "inline-flex",
1458
+ alignItems: "center",
1459
+ gap: "4px",
1460
+ padding: "5px 8px 5px 10px",
1461
+ borderRadius: "var(--iris-radius-md, 6px)",
1462
+ border: `1px solid ${active ? "var(--iris-primary)" : "var(--iris-border)"}`,
1463
+ background: active ? "var(--iris-primary)" : "var(--iris-surface)",
1464
+ color: active ? "var(--iris-primary-foreground, #fff)" : "var(--iris-foreground)",
1465
+ flex: "0 0 auto"
1466
+ }
1467
+ },
1468
+ [label, closeBtn]
1469
+ );
1470
+ };
1471
+ const action = (label, run, disabled = false) => vue.h(IrisDropdownItem, { disabled, onSelect: run }, { default: () => label });
1472
+ const actionsMenu = () => vue.h(IrisDropdown, null, {
1473
+ default: () => [
1474
+ vue.h(
1475
+ IrisDropdownTrigger,
1476
+ { "aria-label": tr("admin.tabActions") },
1477
+ {
1478
+ default: () => vue.h(
1479
+ "span",
1480
+ {
1481
+ "data-iris-tab-actions": "",
1482
+ style: {
1483
+ display: "inline-flex",
1484
+ alignItems: "center",
1485
+ justifyContent: "center",
1486
+ width: "28px",
1487
+ height: "28px",
1488
+ borderRadius: "var(--iris-radius-md, 6px)",
1489
+ border: "1px solid var(--iris-border)",
1490
+ background: "var(--iris-surface)",
1491
+ color: "var(--iris-foreground)",
1492
+ cursor: "pointer"
1493
+ }
1494
+ },
1495
+ [vue.h(IrisIcon, { name: "more-horizontal", size: 16 })]
1496
+ )
1497
+ }
1498
+ ),
1499
+ vue.h(IrisDropdownMenu, null, {
1500
+ default: () => {
1501
+ const key = t.activeKey.value;
1502
+ if (!key) return [action("No active tab", () => {
1503
+ }, true)];
1504
+ return [
1505
+ action(tr("admin.refresh"), () => refresh(key)),
1506
+ action(tr("admin.close"), () => close(key)),
1507
+ vue.h(IrisDropdownSeparator),
1508
+ action(tr("admin.closeOthers"), () => props.nav.closeOthers(key)),
1509
+ action(tr("admin.closeAll"), () => props.nav.closeAll())
1510
+ ];
1511
+ }
1512
+ })
1513
+ ]
1514
+ });
1515
+ return () => vue.h(
1516
+ "div",
1517
+ {
1518
+ ...attrs,
1519
+ "data-iris-admin-tabs": "",
1520
+ style: {
1521
+ display: "flex",
1522
+ alignItems: "center",
1523
+ gap: "6px",
1524
+ padding: "6px 10px",
1525
+ borderBottom: "1px solid var(--iris-border)",
1526
+ background: "var(--iris-background)",
1527
+ ...attrs.style ?? {}
1528
+ }
1529
+ },
1530
+ [
1531
+ vue.h(
1532
+ "div",
1533
+ {
1534
+ "data-iris-tabs-scroll": "",
1535
+ role: "tablist",
1536
+ "aria-label": tr("admin.openPages"),
1537
+ onKeydown: onTablistKeydown,
1538
+ style: {
1539
+ display: "flex",
1540
+ alignItems: "center",
1541
+ gap: "6px",
1542
+ overflowX: "auto",
1543
+ flex: "1"
1544
+ }
1545
+ },
1546
+ t.tabs.value.map((tab) => chip(tab))
1547
+ ),
1548
+ actionsMenu()
1549
+ ]
1550
+ );
1551
+ }
1552
+ });
1553
+ function useStore(store) {
1554
+ const state = vue.shallowRef(store.getState());
1555
+ vue.onScopeDispose(store.subscribe((next) => state.value = next));
1556
+ return state;
1557
+ }
1558
+
1559
+ // src/admin/useAdminShell.ts
1560
+ function useAdminShell(config) {
1561
+ const { defaultActiveKey, onActiveKeyChange, onSelect, tabs } = config;
1562
+ const menus = () => vue.toValue(config.menus);
1563
+ const activeKeyProp = () => vue.toValue(config.activeKey);
1564
+ const activeControlled = () => activeKeyProp() !== void 0;
1565
+ const shell = core.createAdminShell({
1566
+ menus: menus(),
1567
+ defaultActiveKey: activeKeyProp() ?? defaultActiveKey,
1568
+ tabs,
1569
+ onActiveChange: (key, node) => {
1570
+ onActiveKeyChange?.(key);
1571
+ if (node) onSelect?.(key, node);
1572
+ }
1573
+ });
1574
+ const state = useStore(shell.store);
1575
+ vue.watch(menus, (next) => shell.setMenus(next), { flush: "sync" });
1576
+ vue.watchEffect(() => {
1577
+ const prop = activeKeyProp();
1578
+ if (prop !== void 0 && shell.getActiveKey() !== prop) {
1579
+ shell.store.setState({ activeKey: prop });
1580
+ }
1581
+ });
1582
+ const activeKey = vue.computed(
1583
+ () => activeControlled() ? activeKeyProp() ?? "" : state.value.activeKey ?? ""
1584
+ );
1585
+ return {
1586
+ activeKey,
1587
+ navigate: shell.navigate,
1588
+ syncFromTab: shell.syncFromTab,
1589
+ breadcrumb: vue.computed(() => activeKey.value ? core.findNavPath(menus(), activeKey.value) : [])
1590
+ };
1591
+ }
1592
+
1593
+ // src/admin/AdminLayout.ts
1594
+ var IrisAdminLayout = vue.defineComponent({
1595
+ name: "IrisAdminLayout",
1596
+ inheritAttrs: false,
1597
+ props: {
1598
+ /** Normalized nav tree driving menu + breadcrumb. */
1599
+ menus: { type: Array, required: true },
1600
+ /** Active page key (v-model:activeKey). */
1601
+ activeKey: { type: String, default: void 0 },
1602
+ /** Sidebar collapsed (v-model:collapsed); undefined = uncontrolled. */
1603
+ collapsed: { type: Boolean, default: void 0 },
1604
+ defaultCollapsed: { type: Boolean, default: false },
1605
+ mode: { type: String, default: "sidebar" },
1606
+ /** Brand text shown next to the logo when expanded. */
1607
+ appTitle: { type: String, default: "Iris Admin" },
1608
+ /** Optional shared tabs store; when present the tab bar is rendered. */
1609
+ tabs: { type: Object, default: void 0 },
1610
+ showBreadcrumb: { type: Boolean, default: true },
1611
+ sidebarWidth: { type: [Number, String], default: 240 },
1612
+ collapsedWidth: { type: [Number, String], default: 64 }
1613
+ },
1614
+ emits: {
1615
+ "update:activeKey": (_key) => true,
1616
+ "update:collapsed": (_value) => true,
1617
+ select: (_key, _node) => true
1618
+ },
1619
+ setup(props, { emit, slots, attrs }) {
1620
+ const { t } = useI18n();
1621
+ const {
1622
+ activeKey,
1623
+ navigate,
1624
+ syncFromTab,
1625
+ breadcrumb: trail
1626
+ } = useAdminShell({
1627
+ menus: () => props.menus,
1628
+ activeKey: () => props.activeKey,
1629
+ tabs: props.tabs,
1630
+ onActiveKeyChange: (key) => emit("update:activeKey", key),
1631
+ onSelect: (key, node) => emit("select", key, node)
1632
+ });
1633
+ const collapseControlled = vue.computed(() => props.collapsed !== void 0);
1634
+ const internalCollapsed = vue.ref(props.defaultCollapsed);
1635
+ const collapsed = vue.computed(
1636
+ () => collapseControlled.value ? props.collapsed : internalCollapsed.value
1637
+ );
1638
+ const setCollapsed = (value) => {
1639
+ if (!collapseControlled.value) internalCollapsed.value = value;
1640
+ emit("update:collapsed", value);
1641
+ };
1642
+ const onSelect = (_key, node) => navigate(node);
1643
+ if (props.tabs) {
1644
+ const tabsActive = vue.ref(props.tabs.getState().activeKey);
1645
+ const unsubscribe = props.tabs.subscribe((s) => {
1646
+ tabsActive.value = s.activeKey;
1647
+ });
1648
+ vue.onBeforeUnmount(unsubscribe);
1649
+ vue.watch(tabsActive, (key) => {
1650
+ if (key) syncFromTab(key);
1651
+ });
1652
+ }
1653
+ const renderLogo = (state) => {
1654
+ if (slots.logo) return slots.logo(state);
1655
+ return vue.h(
1656
+ "div",
1657
+ {
1658
+ "data-iris-admin-logo": "",
1659
+ style: {
1660
+ display: "flex",
1661
+ alignItems: "center",
1662
+ gap: "10px",
1663
+ height: "52px",
1664
+ padding: "0 16px",
1665
+ color: "var(--iris-foreground)",
1666
+ fontWeight: "700",
1667
+ fontSize: "16px",
1668
+ whiteSpace: "nowrap",
1669
+ overflow: "hidden",
1670
+ flexShrink: 0
1671
+ }
1672
+ },
1673
+ [
1674
+ vue.h(
1675
+ "span",
1676
+ {
1677
+ style: {
1678
+ display: "inline-flex",
1679
+ alignItems: "center",
1680
+ justifyContent: "center",
1681
+ width: "28px",
1682
+ height: "28px",
1683
+ borderRadius: "var(--iris-radius-md, 6px)",
1684
+ background: "var(--iris-primary)",
1685
+ color: "var(--iris-primary-foreground, #fff)",
1686
+ flexShrink: 0
1687
+ }
1688
+ },
1689
+ [vue.h(IrisIcon, { name: "menu", size: 18 })]
1690
+ ),
1691
+ state.collapsed ? null : vue.h("span", props.appTitle)
1692
+ ]
1693
+ );
1694
+ };
1695
+ const collapseToggle = () => vue.h(
1696
+ "button",
1697
+ {
1698
+ type: "button",
1699
+ "data-iris-admin-collapse": "",
1700
+ "aria-label": collapsed.value ? t("admin.expandSidebar") : t("admin.collapseSidebar"),
1701
+ "aria-pressed": collapsed.value ? "true" : "false",
1702
+ style: {
1703
+ display: "inline-flex",
1704
+ alignItems: "center",
1705
+ justifyContent: "center",
1706
+ width: "32px",
1707
+ height: "32px",
1708
+ border: "none",
1709
+ borderRadius: "var(--iris-radius-md, 6px)",
1710
+ background: "transparent",
1711
+ color: "var(--iris-foreground)",
1712
+ cursor: "pointer",
1713
+ flexShrink: 0
1714
+ },
1715
+ onClick: () => setCollapsed(!collapsed.value)
1716
+ },
1717
+ [vue.h(IrisIcon, { name: collapsed.value ? "chevron-right" : "menu", size: 18 })]
1718
+ );
1719
+ const renderHeaderBar = () => vue.h(
1720
+ "div",
1721
+ {
1722
+ "data-iris-admin-headerbar": "",
1723
+ style: {
1724
+ display: "flex",
1725
+ alignItems: "center",
1726
+ gap: "12px",
1727
+ height: "52px",
1728
+ padding: "0 16px",
1729
+ borderBottom: "1px solid var(--iris-border)",
1730
+ background: "var(--iris-background)"
1731
+ }
1732
+ },
1733
+ [
1734
+ collapseToggle(),
1735
+ props.showBreadcrumb ? vue.h(IrisAdminBreadcrumb, { trail: trail.value, hideSingle: false, onSelect }) : null,
1736
+ vue.h("div", { style: { flex: "1" } }),
1737
+ slots.toolbar ? vue.h(
1738
+ "div",
1739
+ {
1740
+ "data-iris-admin-toolbar": "",
1741
+ style: { display: "flex", alignItems: "center", gap: "8px" }
1742
+ },
1743
+ slots.toolbar()
1744
+ ) : null
1745
+ ]
1746
+ );
1747
+ const renderContent = () => vue.h(
1748
+ "div",
1749
+ { "data-iris-admin-content": "", style: { padding: "16px" } },
1750
+ slots.default?.({ activeKey: activeKey.value }) ?? []
1751
+ );
1752
+ return () => {
1753
+ if (props.mode === "full-content") {
1754
+ return vue.h(
1755
+ "div",
1756
+ {
1757
+ ...attrs,
1758
+ "data-iris-admin-layout": "",
1759
+ "data-mode": "full-content",
1760
+ style: {
1761
+ height: "100%",
1762
+ ...attrs.style ?? {}
1763
+ }
1764
+ },
1765
+ slots.default?.({ activeKey: activeKey.value }) ?? []
1766
+ );
1767
+ }
1768
+ return vue.h(
1769
+ IrisSidebarLayout,
1770
+ {
1771
+ ...attrs,
1772
+ collapsed: collapsed.value,
1773
+ width: props.sidebarWidth,
1774
+ collapsedWidth: props.collapsedWidth,
1775
+ "onUpdate:collapsed": setCollapsed,
1776
+ "data-iris-admin-layout": "",
1777
+ "data-mode": "sidebar",
1778
+ style: {
1779
+ height: "100vh",
1780
+ ...attrs.style ?? {}
1781
+ }
1782
+ },
1783
+ {
1784
+ sidebar: (state) => vue.h(
1785
+ "div",
1786
+ {
1787
+ "data-iris-admin-sidebar": "",
1788
+ style: {
1789
+ display: "flex",
1790
+ flexDirection: "column",
1791
+ height: "100%",
1792
+ borderInlineEnd: "1px solid var(--iris-border)",
1793
+ background: "var(--iris-surface)"
1794
+ }
1795
+ },
1796
+ [
1797
+ renderLogo(state),
1798
+ vue.h(
1799
+ "div",
1800
+ { style: { flex: "1", overflowY: "auto", overflowX: "hidden", padding: "8px" } },
1801
+ [
1802
+ vue.h(IrisNavMenu, {
1803
+ items: props.menus,
1804
+ activeKey: activeKey.value,
1805
+ collapsed: state.collapsed,
1806
+ onSelect
1807
+ })
1808
+ ]
1809
+ )
1810
+ ]
1811
+ ),
1812
+ default: () => vue.h(
1813
+ IrisHeaderLayout,
1814
+ { sticky: true },
1815
+ {
1816
+ header: () => vue.h("div", { "data-iris-admin-header": "" }, [
1817
+ renderHeaderBar(),
1818
+ props.tabs ? vue.h(IrisAdminTabs, { nav: props.tabs }) : null
1819
+ ]),
1820
+ default: () => renderContent(),
1821
+ ...slots.footer ? { footer: () => slots.footer() } : {}
1822
+ }
1823
+ )
1824
+ }
1825
+ );
1826
+ };
1827
+ }
1828
+ });
1829
+
1830
+ Object.defineProperty(exports, "createTabsNav", {
1831
+ enumerable: true,
1832
+ get: function () { return core.createTabsNav; }
1833
+ });
1834
+ Object.defineProperty(exports, "findNavNode", {
1835
+ enumerable: true,
1836
+ get: function () { return core.findNavNode; }
1837
+ });
1838
+ Object.defineProperty(exports, "findNavPath", {
1839
+ enumerable: true,
1840
+ get: function () { return core.findNavPath; }
1841
+ });
1842
+ Object.defineProperty(exports, "firstLeaf", {
1843
+ enumerable: true,
1844
+ get: function () { return core.firstLeaf; }
1845
+ });
1846
+ Object.defineProperty(exports, "flattenNav", {
1847
+ enumerable: true,
1848
+ get: function () { return core.flattenNav; }
1849
+ });
1850
+ Object.defineProperty(exports, "isBranch", {
1851
+ enumerable: true,
1852
+ get: function () { return core.isBranch; }
1853
+ });
1854
+ Object.defineProperty(exports, "isClosable", {
1855
+ enumerable: true,
1856
+ get: function () { return core.isClosable; }
1857
+ });
1858
+ Object.defineProperty(exports, "visibleNav", {
1859
+ enumerable: true,
1860
+ get: function () { return core.visibleNav; }
1861
+ });
1862
+ exports.IrisAdminBreadcrumb = IrisAdminBreadcrumb;
1863
+ exports.IrisAdminLayout = IrisAdminLayout;
1864
+ exports.IrisAdminTabs = IrisAdminTabs;
1865
+ exports.IrisNavMenu = IrisNavMenu;
1866
+ exports.useAdminShell = useAdminShell;
1867
+ exports.useTabsNav = useTabsNav;
1868
+ //# sourceMappingURL=admin.cjs.map
1869
+ //# sourceMappingURL=admin.cjs.map