@ansh-ui/vue 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunk-2VSROZ2N.js +1281 -0
  3. package/dist/chunk-2VSROZ2N.js.map +1 -0
  4. package/dist/chunk-5BQEZYG6.cjs +1294 -0
  5. package/dist/chunk-5BQEZYG6.cjs.map +1 -0
  6. package/dist/chunk-6732H4BQ.cjs +28 -0
  7. package/dist/chunk-6732H4BQ.cjs.map +1 -0
  8. package/dist/chunk-6ABPGOJP.js +722 -0
  9. package/dist/chunk-6ABPGOJP.js.map +1 -0
  10. package/dist/chunk-6XJOB6LE.js +62 -0
  11. package/dist/chunk-6XJOB6LE.js.map +1 -0
  12. package/dist/chunk-774WBLDR.cjs +24 -0
  13. package/dist/chunk-774WBLDR.cjs.map +1 -0
  14. package/dist/chunk-B6OXKW5Q.js +567 -0
  15. package/dist/chunk-B6OXKW5Q.js.map +1 -0
  16. package/dist/chunk-DMPNT3MY.cjs +64 -0
  17. package/dist/chunk-DMPNT3MY.cjs.map +1 -0
  18. package/dist/chunk-EISHOEB6.cjs +158 -0
  19. package/dist/chunk-EISHOEB6.cjs.map +1 -0
  20. package/dist/chunk-EVO23GUS.js +223 -0
  21. package/dist/chunk-EVO23GUS.js.map +1 -0
  22. package/dist/chunk-FVKLCGA4.js +1035 -0
  23. package/dist/chunk-FVKLCGA4.js.map +1 -0
  24. package/dist/chunk-IOXGBPOV.cjs +3681 -0
  25. package/dist/chunk-IOXGBPOV.cjs.map +1 -0
  26. package/dist/chunk-J2LA4GRP.cjs +1051 -0
  27. package/dist/chunk-J2LA4GRP.cjs.map +1 -0
  28. package/dist/chunk-JQ4YLXXM.cjs +580 -0
  29. package/dist/chunk-JQ4YLXXM.cjs.map +1 -0
  30. package/dist/chunk-KCA7RNJ4.cjs +781 -0
  31. package/dist/chunk-KCA7RNJ4.cjs.map +1 -0
  32. package/dist/chunk-NJDDNO4C.js +23 -0
  33. package/dist/chunk-NJDDNO4C.js.map +1 -0
  34. package/dist/chunk-OAQ7MDYX.js +22 -0
  35. package/dist/chunk-OAQ7MDYX.js.map +1 -0
  36. package/dist/chunk-PP6OYURM.cjs +238 -0
  37. package/dist/chunk-PP6OYURM.cjs.map +1 -0
  38. package/dist/chunk-SHX2JGID.cjs +269 -0
  39. package/dist/chunk-SHX2JGID.cjs.map +1 -0
  40. package/dist/chunk-SOYRICHU.js +3645 -0
  41. package/dist/chunk-SOYRICHU.js.map +1 -0
  42. package/dist/chunk-T425FIC7.js +773 -0
  43. package/dist/chunk-T425FIC7.js.map +1 -0
  44. package/dist/chunk-TMG3M6TK.js +152 -0
  45. package/dist/chunk-TMG3M6TK.js.map +1 -0
  46. package/dist/chunk-XLLSS464.cjs +286 -0
  47. package/dist/chunk-XLLSS464.cjs.map +1 -0
  48. package/dist/chunk-XZ5ZFLZM.js +280 -0
  49. package/dist/chunk-XZ5ZFLZM.js.map +1 -0
  50. package/dist/chunk-Z7XOFKM4.cjs +733 -0
  51. package/dist/chunk-Z7XOFKM4.cjs.map +1 -0
  52. package/dist/chunk-ZGMXOMCB.js +266 -0
  53. package/dist/chunk-ZGMXOMCB.js.map +1 -0
  54. package/dist/data.cjs +70 -0
  55. package/dist/data.cjs.map +1 -0
  56. package/dist/data.d.cts +963 -0
  57. package/dist/data.d.ts +963 -0
  58. package/dist/data.js +5 -0
  59. package/dist/data.js.map +1 -0
  60. package/dist/form.cjs +151 -0
  61. package/dist/form.cjs.map +1 -0
  62. package/dist/form.d.cts +2639 -0
  63. package/dist/form.d.ts +2639 -0
  64. package/dist/form.js +6 -0
  65. package/dist/form.js.map +1 -0
  66. package/dist/index.cjs +1158 -0
  67. package/dist/index.cjs.map +1 -0
  68. package/dist/index.d.cts +439 -0
  69. package/dist/index.d.ts +439 -0
  70. package/dist/index.js +547 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/layout.cjs +67 -0
  73. package/dist/layout.cjs.map +1 -0
  74. package/dist/layout.d.cts +729 -0
  75. package/dist/layout.d.ts +729 -0
  76. package/dist/layout.js +6 -0
  77. package/dist/layout.js.map +1 -0
  78. package/dist/motion.cjs +50 -0
  79. package/dist/motion.cjs.map +1 -0
  80. package/dist/motion.d.cts +681 -0
  81. package/dist/motion.d.ts +681 -0
  82. package/dist/motion.js +5 -0
  83. package/dist/motion.js.map +1 -0
  84. package/dist/navigation.cjs +48 -0
  85. package/dist/navigation.cjs.map +1 -0
  86. package/dist/navigation.d.cts +515 -0
  87. package/dist/navigation.d.ts +515 -0
  88. package/dist/navigation.js +7 -0
  89. package/dist/navigation.js.map +1 -0
  90. package/dist/overlay.cjs +61 -0
  91. package/dist/overlay.cjs.map +1 -0
  92. package/dist/overlay.d.cts +772 -0
  93. package/dist/overlay.d.ts +772 -0
  94. package/dist/overlay.js +8 -0
  95. package/dist/overlay.js.map +1 -0
  96. package/dist/position-Cn7qP1pn.d.cts +50 -0
  97. package/dist/position-Cn7qP1pn.d.ts +50 -0
  98. package/dist/primitives.cjs +129 -0
  99. package/dist/primitives.cjs.map +1 -0
  100. package/dist/primitives.d.cts +300 -0
  101. package/dist/primitives.d.ts +300 -0
  102. package/dist/primitives.js +8 -0
  103. package/dist/primitives.js.map +1 -0
  104. package/dist/sidebar-CQqEmpid.d.cts +124 -0
  105. package/dist/sidebar-CTZ-Ckko.d.ts +124 -0
  106. package/dist/styles.css +5593 -0
  107. package/dist/utils-IKPG3zcC.d.cts +46 -0
  108. package/dist/utils-IKPG3zcC.d.ts +46 -0
  109. package/package.json +91 -0
@@ -0,0 +1,1281 @@
1
+ import { usePresence } from './chunk-6XJOB6LE.js';
2
+ import { useAnchoredPosition, useDismiss, useScrollLock } from './chunk-XZ5ZFLZM.js';
3
+ import { useFocusTrap, useAutoFocus, useFocusRestore } from './chunk-TMG3M6TK.js';
4
+ import { useControllableState } from './chunk-OAQ7MDYX.js';
5
+ import { useId, cx, resolveSlotContent } from './chunk-NJDDNO4C.js';
6
+ import { defineComponent, ref, provide, h, computed, Teleport, onBeforeUnmount, inject, onMounted } from 'vue';
7
+
8
+ var PopoverKey = /* @__PURE__ */ Symbol("ansh-popover");
9
+ function usePopoverContext(component) {
10
+ const context = inject(PopoverKey, null);
11
+ if (!context) throw new Error(`<${component}> must be rendered inside <Popover>.`);
12
+ return context;
13
+ }
14
+ var Popover = defineComponent({
15
+ name: "AnshPopover",
16
+ props: {
17
+ open: { type: Boolean, default: void 0 },
18
+ defaultOpen: { type: Boolean, default: false },
19
+ placement: { type: String, default: "bottom" },
20
+ /** Gap from the trigger, in px. Default `8`. */
21
+ offset: { type: Number, default: 8 },
22
+ /** Trap focus and block interaction behind. Default `false` — most popovers are non-modal. */
23
+ modal: { type: Boolean, default: false }
24
+ },
25
+ emits: ["update:open"],
26
+ setup(props, { slots, emit }) {
27
+ const [isOpen, setOpen] = useControllableState(
28
+ () => props.open,
29
+ props.defaultOpen,
30
+ (next) => emit("update:open", next)
31
+ );
32
+ const triggerRef = ref(null);
33
+ const contentId = useId("popover");
34
+ provide(PopoverKey, {
35
+ open: isOpen,
36
+ setOpen,
37
+ triggerRef,
38
+ contentId,
39
+ get placement() {
40
+ return props.placement;
41
+ },
42
+ get offset() {
43
+ return props.offset;
44
+ },
45
+ get modal() {
46
+ return props.modal;
47
+ }
48
+ });
49
+ return () => slots.default?.();
50
+ }
51
+ });
52
+ var PopoverTrigger = defineComponent({
53
+ name: "AnshPopoverTrigger",
54
+ inheritAttrs: false,
55
+ props: {
56
+ /** Tag to render. Default `'button'`. */
57
+ as: { type: String, default: "button" }
58
+ },
59
+ setup(props, { slots, attrs }) {
60
+ const ctx = usePopoverContext("PopoverTrigger");
61
+ return () => {
62
+ const { onClick, ...restAttrs } = attrs;
63
+ return h(
64
+ props.as,
65
+ {
66
+ ref: ctx.triggerRef,
67
+ ...props.as === "button" ? { type: "button" } : null,
68
+ "aria-haspopup": "dialog",
69
+ "aria-expanded": ctx.open.value,
70
+ "aria-controls": ctx.open.value ? ctx.contentId : void 0,
71
+ ...restAttrs,
72
+ onClick: (event) => {
73
+ onClick?.(event);
74
+ if (event.defaultPrevented) return;
75
+ ctx.setOpen(!ctx.open.value);
76
+ }
77
+ },
78
+ slots.default?.()
79
+ );
80
+ };
81
+ }
82
+ });
83
+ var PopoverContent = defineComponent({
84
+ name: "AnshPopoverContent",
85
+ inheritAttrs: false,
86
+ props: {
87
+ /** Draw an arrow pointing at the trigger. Default `false`. */
88
+ arrow: { type: Boolean, default: false },
89
+ /** Match the trigger's width — for a popover acting as a dropdown panel. */
90
+ matchWidth: { type: Boolean, default: false },
91
+ /** Accessible name. Required unless the content contains its own heading. */
92
+ label: { type: String, default: void 0 }
93
+ },
94
+ setup(props, { slots, attrs }) {
95
+ const ctx = usePopoverContext("PopoverContent");
96
+ const contentRef = ref(null);
97
+ const { mounted, state } = usePresence(ctx.open, contentRef);
98
+ const { position } = useAnchoredPosition(ctx.triggerRef, contentRef, mounted, {
99
+ placement: ctx.placement,
100
+ offset: ctx.offset,
101
+ matchWidth: props.matchWidth,
102
+ arrowSize: props.arrow ? 10 : 0
103
+ });
104
+ useDismiss(contentRef, ctx.open, () => ctx.setOpen(false), { ignore: [ctx.triggerRef] });
105
+ const trapActive = computed(() => ctx.open.value && ctx.modal);
106
+ useFocusTrap(contentRef, trapActive);
107
+ useAutoFocus(contentRef, ctx.open);
108
+ useFocusRestore(ctx.open);
109
+ return () => {
110
+ if (!mounted.value) return null;
111
+ const { class: attrClass, style: attrStyle, ...restAttrs } = attrs;
112
+ return h(Teleport, { to: "body" }, [
113
+ h(
114
+ "div",
115
+ {
116
+ ref: contentRef,
117
+ id: ctx.contentId,
118
+ role: "dialog",
119
+ "aria-modal": ctx.modal || void 0,
120
+ ...props.label ? { "aria-label": props.label } : null,
121
+ tabindex: "-1",
122
+ "data-state": state.value,
123
+ "data-side": position.value.side,
124
+ class: cx("ansh-popover", attrClass),
125
+ style: [position.value.style, attrStyle],
126
+ ...restAttrs
127
+ },
128
+ [
129
+ slots.default?.(),
130
+ props.arrow && position.value.arrowStyle ? h("span", {
131
+ class: "ansh-popover__arrow",
132
+ style: position.value.arrowStyle,
133
+ "aria-hidden": "true"
134
+ }) : null
135
+ ]
136
+ )
137
+ ]);
138
+ };
139
+ }
140
+ });
141
+ var HoverCard = defineComponent({
142
+ name: "AnshHoverCard",
143
+ props: {
144
+ /** Delay before opening, in ms. Default `500`. */
145
+ openDelay: { type: Number, default: 500 },
146
+ /** Delay before closing, in ms. Default `200`. */
147
+ closeDelay: { type: Number, default: 200 },
148
+ placement: { type: String, default: "bottom" }
149
+ },
150
+ setup(props, { slots, attrs }) {
151
+ const open = ref(false);
152
+ const triggerRef = ref(null);
153
+ const cardRef = ref(null);
154
+ const id = useId("hovercard");
155
+ let timer;
156
+ const { mounted, state } = usePresence(open, cardRef);
157
+ const { position } = useAnchoredPosition(triggerRef, cardRef, mounted, {
158
+ placement: props.placement,
159
+ offset: 8
160
+ });
161
+ const schedule = (next) => {
162
+ clearTimeout(timer);
163
+ timer = setTimeout(
164
+ () => {
165
+ open.value = next;
166
+ },
167
+ next ? props.openDelay : props.closeDelay
168
+ );
169
+ };
170
+ onBeforeUnmount(() => clearTimeout(timer));
171
+ return () => h("span", { style: { display: "contents" } }, [
172
+ h(
173
+ "span",
174
+ {
175
+ ref: triggerRef,
176
+ class: "ansh-hovercard__trigger",
177
+ // `aria-describedby` rather than `aria-details`: support for details is still patchy,
178
+ // and a description is the honest relationship for supplementary content.
179
+ "aria-describedby": open.value ? id : void 0,
180
+ onMouseenter: () => schedule(true),
181
+ onMouseleave: () => schedule(false),
182
+ onFocus: () => {
183
+ open.value = true;
184
+ },
185
+ onBlur: () => {
186
+ open.value = false;
187
+ }
188
+ },
189
+ slots.default?.()
190
+ ),
191
+ mounted.value ? h(Teleport, { to: "body" }, [
192
+ h(
193
+ "div",
194
+ {
195
+ ref: cardRef,
196
+ id,
197
+ role: "tooltip",
198
+ "data-state": state.value,
199
+ class: cx("ansh-hovercard", attrs.class),
200
+ style: position.value.style,
201
+ // Keeping the card open while the pointer is over it is what makes its contents
202
+ // clickable at all.
203
+ onMouseenter: () => schedule(true),
204
+ onMouseleave: () => schedule(false)
205
+ },
206
+ slots.content?.()
207
+ )
208
+ ]) : null
209
+ ]);
210
+ }
211
+ });
212
+ var TooltipGroupKey = /* @__PURE__ */ Symbol("ansh-tooltip-group");
213
+ var TooltipProvider = defineComponent({
214
+ name: "AnshTooltipProvider",
215
+ props: {
216
+ /** Delay before the first tooltip in a group opens, in ms. Default `600`. */
217
+ delay: { type: Number, default: 600 }
218
+ },
219
+ setup(props, { slots }) {
220
+ const active = ref(false);
221
+ provide(TooltipGroupKey, {
222
+ active,
223
+ setActive: (next) => {
224
+ active.value = next;
225
+ },
226
+ get delay() {
227
+ return props.delay;
228
+ }
229
+ });
230
+ return () => slots.default?.();
231
+ }
232
+ });
233
+ var Tooltip = defineComponent({
234
+ name: "AnshTooltip",
235
+ inheritAttrs: false,
236
+ props: {
237
+ /** The tooltip text. Keep it short — it is read as one description. */
238
+ content: { type: [String, Object, Function], required: true },
239
+ placement: { type: String, default: "top" },
240
+ /** Override the group delay, in ms. */
241
+ delay: { type: Number, default: void 0 },
242
+ /** Draw an arrow. Default `true`. */
243
+ arrow: { type: Boolean, default: true },
244
+ disabled: { type: Boolean, default: false }
245
+ },
246
+ setup(props, { slots, attrs }) {
247
+ const group = inject(TooltipGroupKey, null);
248
+ const open = ref(false);
249
+ const triggerRef = ref(null);
250
+ const tipRef = ref(null);
251
+ let timer;
252
+ const id = useId("tooltip");
253
+ const { mounted, state } = usePresence(open, tipRef);
254
+ const { position } = useAnchoredPosition(triggerRef, tipRef, mounted, {
255
+ placement: props.placement,
256
+ offset: 6,
257
+ arrowSize: props.arrow ? 8 : 0
258
+ });
259
+ const effectiveDelay = () => props.delay ?? group?.delay ?? 600;
260
+ const show = (immediate = false) => {
261
+ if (props.disabled) return;
262
+ clearTimeout(timer);
263
+ if (immediate || group?.active.value) {
264
+ open.value = true;
265
+ group?.setActive(true);
266
+ return;
267
+ }
268
+ timer = setTimeout(() => {
269
+ open.value = true;
270
+ group?.setActive(true);
271
+ }, effectiveDelay());
272
+ };
273
+ const hide = () => {
274
+ clearTimeout(timer);
275
+ open.value = false;
276
+ timer = setTimeout(() => group?.setActive(false), 200);
277
+ };
278
+ onBeforeUnmount(() => clearTimeout(timer));
279
+ useDismiss(
280
+ tipRef,
281
+ open,
282
+ () => {
283
+ open.value = false;
284
+ group?.setActive(false);
285
+ },
286
+ { outsidePress: false, escapeKey: true }
287
+ );
288
+ return () => {
289
+ const { class: attrClass, ...restAttrs } = attrs;
290
+ return h(
291
+ "span",
292
+ {
293
+ // No box of its own — see the module comment on why this wrapper exists at all.
294
+ style: { display: "contents" }
295
+ },
296
+ [
297
+ h(
298
+ "span",
299
+ {
300
+ ...restAttrs,
301
+ ref: triggerRef,
302
+ class: cx("ansh-tooltip-trigger", attrClass),
303
+ "aria-describedby": open.value ? id : void 0,
304
+ onMouseenter: () => show(),
305
+ onMouseleave: hide,
306
+ onFocusin: () => show(true),
307
+ onFocusout: hide
308
+ },
309
+ slots.default?.()
310
+ ),
311
+ mounted.value && !props.disabled ? h(Teleport, { to: "body" }, [
312
+ h(
313
+ "div",
314
+ {
315
+ ref: tipRef,
316
+ id,
317
+ role: "tooltip",
318
+ "data-state": state.value,
319
+ "data-side": position.value.side,
320
+ class: "ansh-tooltip",
321
+ style: position.value.style
322
+ },
323
+ [
324
+ resolveSlotContent(props.content),
325
+ props.arrow && position.value.arrowStyle ? h("span", {
326
+ class: "ansh-tooltip__arrow",
327
+ style: position.value.arrowStyle,
328
+ "aria-hidden": "true"
329
+ }) : null
330
+ ]
331
+ )
332
+ ]) : null
333
+ ]
334
+ );
335
+ };
336
+ }
337
+ });
338
+ var Dialog = defineComponent({
339
+ name: "AnshDialog",
340
+ inheritAttrs: false,
341
+ props: {
342
+ open: { type: Boolean, default: void 0 },
343
+ defaultOpen: { type: Boolean, default: false },
344
+ /** The dialog's accessible name. Required — a dialog announced as just "dialog" is useless. */
345
+ title: { type: [String, Object, Function], required: true },
346
+ /** Supporting text, wired to `aria-describedby`. */
347
+ description: { type: [String, Object, Function], default: void 0 },
348
+ size: { type: String, default: "md" },
349
+ /** Show the close button. Default `true`. */
350
+ showClose: { type: Boolean, default: true },
351
+ /** Allow closing via scrim click and Escape. Default `true`. */
352
+ dismissable: { type: Boolean, default: true },
353
+ /** Element to focus on open. Defaults to the first tabbable one. */
354
+ initialFocus: { type: Object, default: void 0 }
355
+ },
356
+ emits: ["update:open"],
357
+ setup(props, { slots, attrs, emit }) {
358
+ const [isOpen, setOpen] = useControllableState(
359
+ () => props.open,
360
+ props.defaultOpen,
361
+ (next) => emit("update:open", next)
362
+ );
363
+ const panelRef = ref(null);
364
+ const id = useId("dialog");
365
+ const titleId = `${id}-title`;
366
+ const descriptionId = `${id}-description`;
367
+ const { mounted, state } = usePresence(isOpen, panelRef);
368
+ const close = () => {
369
+ if (props.dismissable) setOpen(false);
370
+ };
371
+ useDismiss(panelRef, isOpen, close, {
372
+ outsidePress: props.dismissable,
373
+ escapeKey: props.dismissable
374
+ });
375
+ useFocusTrap(panelRef, isOpen);
376
+ useAutoFocus(panelRef, isOpen, props.initialFocus);
377
+ useFocusRestore(isOpen);
378
+ useScrollLock(isOpen);
379
+ return () => {
380
+ if (!mounted.value) return null;
381
+ const { class: attrClass, ...restAttrs } = attrs;
382
+ return h(Teleport, { to: "body" }, [
383
+ h("div", { class: "ansh-dialog__layer", "data-state": state.value }, [
384
+ // The scrim is not a button and takes no focus — it's decorative. Dismissal is handled
385
+ // by `useDismiss` on a pointerdown outside the panel, which also covers the gap around
386
+ // the panel that isn't the scrim element itself.
387
+ h("div", {
388
+ class: "ansh-dialog__scrim",
389
+ "data-state": state.value,
390
+ "aria-hidden": "true"
391
+ }),
392
+ h(
393
+ "div",
394
+ {
395
+ ref: panelRef,
396
+ role: "dialog",
397
+ "aria-modal": "true",
398
+ "aria-labelledby": titleId,
399
+ ...props.description ? { "aria-describedby": descriptionId } : null,
400
+ tabindex: "-1",
401
+ "data-state": state.value,
402
+ class: cx("ansh-dialog", `ansh-dialog--${props.size}`, attrClass),
403
+ ...restAttrs
404
+ },
405
+ [
406
+ h("header", { class: "ansh-dialog__header" }, [
407
+ h("div", { class: "ansh-dialog__heading" }, [
408
+ h("h2", { class: "ansh-dialog__title", id: titleId }, [
409
+ resolveSlotContent(props.title)
410
+ ]),
411
+ props.description ? h("p", { class: "ansh-dialog__description", id: descriptionId }, [
412
+ resolveSlotContent(props.description)
413
+ ]) : null
414
+ ]),
415
+ props.showClose ? h(
416
+ "button",
417
+ {
418
+ type: "button",
419
+ class: "ansh-dialog__close ansh-focusable",
420
+ "aria-label": "Close dialog",
421
+ onClick: () => setOpen(false)
422
+ },
423
+ [
424
+ h(
425
+ "svg",
426
+ {
427
+ viewBox: "0 0 16 16",
428
+ width: "14",
429
+ height: "14",
430
+ "aria-hidden": "true"
431
+ },
432
+ [
433
+ h("path", {
434
+ d: "M4 4l8 8M12 4l-8 8",
435
+ stroke: "currentColor",
436
+ "stroke-width": "1.75",
437
+ "stroke-linecap": "round",
438
+ fill: "none"
439
+ })
440
+ ]
441
+ )
442
+ ]
443
+ ) : null
444
+ ]),
445
+ slots.default ? h("div", { class: "ansh-dialog__body" }, slots.default()) : null,
446
+ slots.footer ? h("footer", { class: "ansh-dialog__footer" }, slots.footer()) : null
447
+ ]
448
+ )
449
+ ])
450
+ ]);
451
+ };
452
+ }
453
+ });
454
+ var AlertDialog = defineComponent({
455
+ name: "AnshAlertDialog",
456
+ inheritAttrs: false,
457
+ props: {
458
+ open: { type: Boolean, default: void 0 },
459
+ defaultOpen: { type: Boolean, default: false },
460
+ title: { type: [String, Object, Function], required: true },
461
+ description: { type: [String, Object, Function], required: true },
462
+ /** Confirm button text. Default `'Confirm'`. */
463
+ confirmLabel: { type: String, default: "Confirm" },
464
+ cancelLabel: { type: String, default: "Cancel" },
465
+ /** Style the confirm action as destructive. */
466
+ destructive: { type: Boolean, default: false },
467
+ /** Disable the buttons while an async confirm runs. */
468
+ busy: { type: Boolean, default: false }
469
+ },
470
+ emits: ["update:open", "confirm", "cancel"],
471
+ setup(props, { attrs, emit }) {
472
+ const [isOpen, setOpen] = useControllableState(
473
+ () => props.open,
474
+ props.defaultOpen,
475
+ (next) => emit("update:open", next)
476
+ );
477
+ const panelRef = ref(null);
478
+ const cancelRef = ref(null);
479
+ const id = useId("alertdialog");
480
+ const titleId = `${id}-title`;
481
+ const descriptionId = `${id}-description`;
482
+ const { mounted, state } = usePresence(isOpen, panelRef);
483
+ const cancel = () => {
484
+ emit("cancel");
485
+ setOpen(false);
486
+ };
487
+ useDismiss(panelRef, isOpen, cancel, { outsidePress: false, escapeKey: true });
488
+ useFocusTrap(panelRef, isOpen);
489
+ useAutoFocus(panelRef, isOpen, cancelRef);
490
+ useFocusRestore(isOpen);
491
+ useScrollLock(isOpen);
492
+ return () => {
493
+ if (!mounted.value) return null;
494
+ const { class: attrClass, ...restAttrs } = attrs;
495
+ return h(Teleport, { to: "body" }, [
496
+ h("div", { class: "ansh-dialog__layer", "data-state": state.value }, [
497
+ h("div", {
498
+ class: "ansh-dialog__scrim",
499
+ "data-state": state.value,
500
+ "aria-hidden": "true"
501
+ }),
502
+ h(
503
+ "div",
504
+ {
505
+ ref: panelRef,
506
+ role: "alertdialog",
507
+ "aria-modal": "true",
508
+ "aria-labelledby": titleId,
509
+ "aria-describedby": descriptionId,
510
+ tabindex: "-1",
511
+ "data-state": state.value,
512
+ class: cx(
513
+ "ansh-dialog",
514
+ "ansh-dialog--sm",
515
+ "ansh-dialog--alert",
516
+ attrClass
517
+ ),
518
+ ...restAttrs
519
+ },
520
+ [
521
+ h("div", { class: "ansh-dialog__body" }, [
522
+ props.destructive ? h(
523
+ "span",
524
+ {
525
+ class: "ansh-dialog__alert-icon ansh-dialog__alert-icon--danger",
526
+ "aria-hidden": "true"
527
+ },
528
+ [
529
+ h(
530
+ "svg",
531
+ {
532
+ viewBox: "0 0 24 24",
533
+ width: "20",
534
+ height: "20",
535
+ fill: "none",
536
+ stroke: "currentColor",
537
+ "stroke-width": "1.75"
538
+ },
539
+ [
540
+ h("path", { d: "M12 8v5", "stroke-linecap": "round" }),
541
+ h("circle", {
542
+ cx: "12",
543
+ cy: "16.5",
544
+ r: "1",
545
+ fill: "currentColor",
546
+ stroke: "none"
547
+ }),
548
+ h("path", {
549
+ d: "M10.3 3.9L2.6 17.4A2 2 0 004.3 20.4h15.4a2 2 0 001.7-3L13.7 3.9a2 2 0 00-3.4 0z",
550
+ "stroke-linejoin": "round"
551
+ })
552
+ ]
553
+ )
554
+ ]
555
+ ) : null,
556
+ h("h2", { class: "ansh-dialog__title", id: titleId }, [
557
+ resolveSlotContent(props.title)
558
+ ]),
559
+ h("p", { class: "ansh-dialog__description", id: descriptionId }, [
560
+ resolveSlotContent(props.description)
561
+ ])
562
+ ]),
563
+ h("footer", { class: "ansh-dialog__footer" }, [
564
+ h(
565
+ "button",
566
+ {
567
+ ref: cancelRef,
568
+ type: "button",
569
+ class: "ansh-btn ansh-btn--ghost ansh-btn--sm ansh-focusable",
570
+ disabled: props.busy,
571
+ onClick: cancel
572
+ },
573
+ [props.cancelLabel]
574
+ ),
575
+ h(
576
+ "button",
577
+ {
578
+ type: "button",
579
+ class: cx(
580
+ "ansh-btn",
581
+ "ansh-btn--sm",
582
+ props.destructive ? "ansh-btn--danger" : "ansh-btn--solid",
583
+ "ansh-focusable"
584
+ ),
585
+ disabled: props.busy,
586
+ onClick: () => emit("confirm")
587
+ },
588
+ [
589
+ props.busy ? h("span", { class: "ansh-spinner ansh-spinner--sm", "aria-hidden": "true" }) : null,
590
+ props.confirmLabel
591
+ ]
592
+ )
593
+ ])
594
+ ]
595
+ )
596
+ ])
597
+ ]);
598
+ };
599
+ }
600
+ });
601
+ var DialogFooter = defineComponent({
602
+ name: "AnshDialogFooter",
603
+ setup(_props, { slots, attrs }) {
604
+ return () => h("div", { class: cx("ansh-dialog__actions", attrs.class) }, slots.default?.());
605
+ }
606
+ });
607
+ var Drawer = defineComponent({
608
+ name: "AnshDrawer",
609
+ inheritAttrs: false,
610
+ props: {
611
+ open: { type: Boolean, default: void 0 },
612
+ defaultOpen: { type: Boolean, default: false },
613
+ /** Which edge it slides from. Default `'right'`. */
614
+ side: { type: String, default: "right" },
615
+ title: { type: [String, Object, Function], required: true },
616
+ description: { type: [String, Object, Function], default: void 0 },
617
+ /** Width for left/right, height for top/bottom. Default `'22rem'`. */
618
+ size: { type: String, default: "22rem" },
619
+ showClose: { type: Boolean, default: true },
620
+ /** Block interaction behind and lock scroll. Default `true`. */
621
+ modal: { type: Boolean, default: true },
622
+ dismissable: { type: Boolean, default: true }
623
+ },
624
+ emits: ["update:open"],
625
+ setup(props, { slots, attrs, emit }) {
626
+ const [isOpen, setOpen] = useControllableState(
627
+ () => props.open,
628
+ props.defaultOpen,
629
+ (next) => emit("update:open", next)
630
+ );
631
+ const panelRef = ref(null);
632
+ const id = useId("drawer");
633
+ const titleId = `${id}-title`;
634
+ const descriptionId = `${id}-description`;
635
+ const { mounted, state } = usePresence(isOpen, panelRef);
636
+ const close = () => {
637
+ if (props.dismissable) setOpen(false);
638
+ };
639
+ useDismiss(panelRef, isOpen, close, {
640
+ outsidePress: props.dismissable && props.modal,
641
+ escapeKey: props.dismissable
642
+ });
643
+ const trapActive = computed(() => isOpen.value && props.modal);
644
+ useFocusTrap(panelRef, trapActive);
645
+ useAutoFocus(panelRef, isOpen);
646
+ useFocusRestore(isOpen);
647
+ const scrollLockActive = computed(() => isOpen.value && props.modal);
648
+ useScrollLock(scrollLockActive);
649
+ return () => {
650
+ if (!mounted.value) return null;
651
+ const { class: attrClass, ...restAttrs } = attrs;
652
+ const horizontal = props.side === "left" || props.side === "right";
653
+ return h(Teleport, { to: "body" }, [
654
+ h(
655
+ "div",
656
+ {
657
+ class: cx("ansh-drawer__layer", !props.modal && "ansh-drawer__layer--inline"),
658
+ "data-state": state.value
659
+ },
660
+ [
661
+ props.modal ? h("div", {
662
+ class: "ansh-dialog__scrim",
663
+ "data-state": state.value,
664
+ "aria-hidden": "true"
665
+ }) : null,
666
+ h(
667
+ "div",
668
+ {
669
+ ref: panelRef,
670
+ role: "dialog",
671
+ "aria-modal": props.modal || void 0,
672
+ "aria-labelledby": titleId,
673
+ ...props.description ? { "aria-describedby": descriptionId } : null,
674
+ tabindex: "-1",
675
+ "data-state": state.value,
676
+ "data-side": props.side,
677
+ class: cx("ansh-drawer", `ansh-drawer--${props.side}`, attrClass),
678
+ // The size travels as a custom property so the closed-state transform can be
679
+ // written in terms of it — `translateX(100%)` alone breaks when the panel has a
680
+ // max-width.
681
+ style: {
682
+ [horizontal ? "width" : "height"]: props.size,
683
+ "--ansh-drawer-size": props.size
684
+ },
685
+ ...restAttrs
686
+ },
687
+ [
688
+ h("header", { class: "ansh-drawer__header" }, [
689
+ h("div", { class: "ansh-dialog__heading" }, [
690
+ h("h2", { class: "ansh-dialog__title", id: titleId }, [
691
+ resolveSlotContent(props.title)
692
+ ]),
693
+ props.description ? h("p", { class: "ansh-dialog__description", id: descriptionId }, [
694
+ resolveSlotContent(props.description)
695
+ ]) : null
696
+ ]),
697
+ props.showClose ? h(
698
+ "button",
699
+ {
700
+ type: "button",
701
+ class: "ansh-dialog__close ansh-focusable",
702
+ "aria-label": "Close panel",
703
+ onClick: () => setOpen(false)
704
+ },
705
+ [
706
+ h(
707
+ "svg",
708
+ {
709
+ viewBox: "0 0 16 16",
710
+ width: "14",
711
+ height: "14",
712
+ "aria-hidden": "true"
713
+ },
714
+ [
715
+ h("path", {
716
+ d: "M4 4l8 8M12 4l-8 8",
717
+ stroke: "currentColor",
718
+ "stroke-width": "1.75",
719
+ "stroke-linecap": "round",
720
+ fill: "none"
721
+ })
722
+ ]
723
+ )
724
+ ]
725
+ ) : null
726
+ ]),
727
+ h("div", { class: "ansh-drawer__body" }, slots.default?.()),
728
+ slots.footer ? h("footer", { class: "ansh-drawer__footer" }, slots.footer()) : null
729
+ ]
730
+ )
731
+ ]
732
+ )
733
+ ]);
734
+ };
735
+ }
736
+ });
737
+ function flatten(items) {
738
+ const rows = [];
739
+ for (const entry of items) {
740
+ if ("type" in entry && (entry.type === "separator" || entry.type === "label")) continue;
741
+ if ("type" in entry && entry.type === "radio") {
742
+ entry.options.forEach((option, optionIndex) => {
743
+ rows.push({ entry, optionIndex, disabled: option.disabled ?? false });
744
+ });
745
+ continue;
746
+ }
747
+ rows.push({ entry, disabled: entry.disabled ?? false });
748
+ }
749
+ return rows;
750
+ }
751
+ var DropdownMenu = defineComponent({
752
+ name: "AnshDropdownMenu",
753
+ inheritAttrs: false,
754
+ props: {
755
+ items: { type: Array, required: true },
756
+ placement: { type: String, default: "bottom-start" },
757
+ /** Accessible name for the menu itself. */
758
+ label: { type: String, default: "Menu" },
759
+ open: { type: Boolean, default: void 0 },
760
+ defaultOpen: { type: Boolean, default: false }
761
+ },
762
+ emits: ["update:open"],
763
+ setup(props, { slots, attrs, emit }) {
764
+ const [isOpen, setOpen] = useControllableState(
765
+ () => props.open,
766
+ props.defaultOpen,
767
+ (next) => emit("update:open", next)
768
+ );
769
+ const triggerRef = ref(null);
770
+ const menuRef = ref(null);
771
+ const menuId = useId("menu");
772
+ const { mounted, state } = usePresence(isOpen, menuRef);
773
+ const { position } = useAnchoredPosition(triggerRef, menuRef, mounted, {
774
+ placement: props.placement,
775
+ offset: 6
776
+ });
777
+ useDismiss(menuRef, isOpen, () => setOpen(false), { ignore: [triggerRef] });
778
+ return () => {
779
+ const panelClass = attrs.class;
780
+ return h("span", { style: { display: "contents" } }, [
781
+ h(
782
+ "span",
783
+ {
784
+ ref: triggerRef,
785
+ class: "ansh-menu-trigger",
786
+ "aria-haspopup": "menu",
787
+ "aria-expanded": isOpen.value,
788
+ "aria-controls": isOpen.value ? menuId : void 0,
789
+ onClick: () => setOpen(!isOpen.value),
790
+ onKeydown: (event) => {
791
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
792
+ event.preventDefault();
793
+ setOpen(true);
794
+ }
795
+ }
796
+ },
797
+ slots.default?.()
798
+ ),
799
+ mounted.value ? h(Teleport, { to: "body" }, [
800
+ h(MenuSurface, {
801
+ panelRef: menuRef,
802
+ id: menuId,
803
+ items: props.items,
804
+ label: props.label,
805
+ state: state.value,
806
+ positionStyle: position.value.style,
807
+ panelClass,
808
+ onClose: () => {
809
+ setOpen(false);
810
+ triggerRef.value?.focus?.();
811
+ }
812
+ })
813
+ ]) : null
814
+ ]);
815
+ };
816
+ }
817
+ });
818
+ var ContextMenu = defineComponent({
819
+ name: "AnshContextMenu",
820
+ inheritAttrs: false,
821
+ props: {
822
+ items: { type: Array, required: true },
823
+ label: { type: String, default: "Context menu" }
824
+ },
825
+ setup(props, { slots, attrs }) {
826
+ const point = ref(null);
827
+ const menuRef = ref(null);
828
+ const anchorRef = ref(null);
829
+ const menuId = useId("contextmenu");
830
+ const open = computed(() => point.value !== null);
831
+ const { mounted, state } = usePresence(open, menuRef);
832
+ const { position } = useAnchoredPosition(anchorRef, menuRef, mounted, {
833
+ placement: "bottom-start",
834
+ offset: 2
835
+ });
836
+ useDismiss(menuRef, open, () => {
837
+ point.value = null;
838
+ });
839
+ return () => {
840
+ const panelClass = attrs.class;
841
+ return h("span", { style: { display: "contents" } }, [
842
+ h(
843
+ "span",
844
+ {
845
+ class: "ansh-contextmenu__area",
846
+ onContextmenu: (event) => {
847
+ event.preventDefault();
848
+ point.value = { x: event.clientX, y: event.clientY };
849
+ },
850
+ onKeydown: (event) => {
851
+ if (event.key === "ContextMenu" || event.shiftKey && event.key === "F10") {
852
+ event.preventDefault();
853
+ const rect = event.currentTarget.getBoundingClientRect();
854
+ point.value = { x: rect.left + 8, y: rect.top + 8 };
855
+ }
856
+ }
857
+ },
858
+ slots.default?.()
859
+ ),
860
+ // A zero-size anchor placed at the pointer. Cheaper than teaching the positioner about
861
+ // virtual elements, and it inherits flip/shift behaviour for free.
862
+ point.value ? h("span", {
863
+ ref: anchorRef,
864
+ "aria-hidden": "true",
865
+ style: {
866
+ position: "fixed",
867
+ left: `${point.value.x}px`,
868
+ top: `${point.value.y}px`,
869
+ width: "0px",
870
+ height: "0px"
871
+ }
872
+ }) : null,
873
+ mounted.value ? h(Teleport, { to: "body" }, [
874
+ h(MenuSurface, {
875
+ panelRef: menuRef,
876
+ id: menuId,
877
+ items: props.items,
878
+ label: props.label,
879
+ state: state.value,
880
+ positionStyle: position.value.style,
881
+ panelClass,
882
+ onClose: () => {
883
+ point.value = null;
884
+ }
885
+ })
886
+ ]) : null
887
+ ]);
888
+ };
889
+ }
890
+ });
891
+ var MenuSurface = defineComponent({
892
+ name: "AnshMenuSurface",
893
+ props: {
894
+ panelRef: { type: Object, required: true },
895
+ id: { type: String, required: true },
896
+ items: { type: Array, required: true },
897
+ label: { type: String, required: true },
898
+ state: { type: String, required: true },
899
+ // Named `positionStyle`/`panelClass` rather than `style`/`class` — Vue merges those two
900
+ // particular prop names onto a component's root element automatically regardless of whether
901
+ // they're declared, which would double up the class list.
902
+ positionStyle: { type: Object, required: true },
903
+ panelClass: { type: String, default: void 0 },
904
+ onClose: { type: Function, required: true }
905
+ },
906
+ setup(props) {
907
+ const rows = computed(() => flatten(props.items));
908
+ const itemRefs = ref([]);
909
+ const active = ref(rows.value.findIndex((row) => !row.disabled));
910
+ const openSubmenu = ref(null);
911
+ let typeaheadBuffer = "";
912
+ let typeaheadTimer;
913
+ onMounted(() => {
914
+ itemRefs.value[active.value]?.focus();
915
+ });
916
+ onBeforeUnmount(() => clearTimeout(typeaheadTimer));
917
+ const move = (delta) => {
918
+ const list = rows.value;
919
+ if (list.length === 0) return;
920
+ let next = active.value;
921
+ for (let attempts = 0; attempts < list.length; attempts++) {
922
+ next = (next + delta + list.length) % list.length;
923
+ if (!list[next]?.disabled) break;
924
+ }
925
+ active.value = next;
926
+ itemRefs.value[next]?.focus();
927
+ };
928
+ const activate = (index) => {
929
+ const row = rows.value[index];
930
+ if (!row || row.disabled) return;
931
+ const { entry } = row;
932
+ if ("type" in entry && entry.type === "submenu") {
933
+ openSubmenu.value = index;
934
+ return;
935
+ }
936
+ if ("type" in entry && entry.type === "checkbox") {
937
+ entry.onChange(!entry.checked);
938
+ return;
939
+ }
940
+ if ("type" in entry && entry.type === "radio") {
941
+ entry.onChange(entry.options[row.optionIndex].value);
942
+ props.onClose();
943
+ return;
944
+ }
945
+ entry.onSelect?.();
946
+ props.onClose();
947
+ };
948
+ const onKeydown = (event) => {
949
+ switch (event.key) {
950
+ case "ArrowDown":
951
+ event.preventDefault();
952
+ move(1);
953
+ break;
954
+ case "ArrowUp":
955
+ event.preventDefault();
956
+ move(-1);
957
+ break;
958
+ case "Home":
959
+ event.preventDefault();
960
+ active.value = 0;
961
+ itemRefs.value[0]?.focus();
962
+ break;
963
+ case "End":
964
+ event.preventDefault();
965
+ active.value = rows.value.length - 1;
966
+ itemRefs.value[rows.value.length - 1]?.focus();
967
+ break;
968
+ case "Enter":
969
+ case " ":
970
+ event.preventDefault();
971
+ activate(active.value);
972
+ break;
973
+ case "ArrowRight": {
974
+ const row = rows.value[active.value];
975
+ if (row && "type" in row.entry && row.entry.type === "submenu") {
976
+ event.preventDefault();
977
+ openSubmenu.value = active.value;
978
+ }
979
+ break;
980
+ }
981
+ case "Escape":
982
+ event.preventDefault();
983
+ props.onClose();
984
+ break;
985
+ case "Tab":
986
+ event.preventDefault();
987
+ props.onClose();
988
+ break;
989
+ default: {
990
+ if (event.key.length !== 1) return;
991
+ clearTimeout(typeaheadTimer);
992
+ typeaheadBuffer += event.key.toLowerCase();
993
+ typeaheadTimer = setTimeout(() => {
994
+ typeaheadBuffer = "";
995
+ }, 600);
996
+ const match = rows.value.findIndex((row, index) => {
997
+ if (row.disabled || index === active.value) return false;
998
+ const text = typeof row.entry.label === "string" ? row.entry.label : "";
999
+ return text.toLowerCase().startsWith(typeaheadBuffer);
1000
+ });
1001
+ if (match >= 0) {
1002
+ active.value = match;
1003
+ itemRefs.value[match]?.focus();
1004
+ }
1005
+ }
1006
+ }
1007
+ };
1008
+ return () => {
1009
+ let rowIndex = -1;
1010
+ return h(
1011
+ "div",
1012
+ {
1013
+ ref: props.panelRef,
1014
+ id: props.id,
1015
+ role: "menu",
1016
+ "aria-label": props.label,
1017
+ "data-state": props.state,
1018
+ class: cx("ansh-menu", props.panelClass),
1019
+ style: props.positionStyle,
1020
+ onKeydown
1021
+ },
1022
+ props.items.map((entry, entryIndex) => {
1023
+ if ("type" in entry && entry.type === "separator") {
1024
+ return h("div", {
1025
+ key: `sep-${entryIndex}`,
1026
+ role: "separator",
1027
+ class: "ansh-menu__separator"
1028
+ });
1029
+ }
1030
+ if ("type" in entry && entry.type === "label") {
1031
+ return h(
1032
+ "div",
1033
+ { key: `label-${entryIndex}`, role: "presentation", class: "ansh-menu__label" },
1034
+ [resolveSlotContent(entry.label)]
1035
+ );
1036
+ }
1037
+ if ("type" in entry && entry.type === "radio") {
1038
+ return h(
1039
+ "div",
1040
+ { key: `radio-${entryIndex}`, role: "group", class: "ansh-menu__group" },
1041
+ entry.options.map((option) => {
1042
+ rowIndex++;
1043
+ const index2 = rowIndex;
1044
+ return h(
1045
+ "div",
1046
+ {
1047
+ key: option.value,
1048
+ ref: (node) => {
1049
+ itemRefs.value[index2] = node;
1050
+ },
1051
+ role: "menuitemradio",
1052
+ "aria-checked": entry.value === option.value,
1053
+ "aria-disabled": option.disabled || void 0,
1054
+ tabindex: index2 === active.value ? "0" : "-1",
1055
+ class: cx("ansh-menu__item", option.disabled && "ansh-menu__item--disabled"),
1056
+ onClick: () => activate(index2),
1057
+ onMouseenter: () => {
1058
+ if (!option.disabled) active.value = index2;
1059
+ }
1060
+ },
1061
+ [
1062
+ h("span", {
1063
+ class: cx(
1064
+ "ansh-menu__indicator",
1065
+ entry.value === option.value && "ansh-menu__indicator--on"
1066
+ ),
1067
+ "aria-hidden": "true"
1068
+ }),
1069
+ h("span", { class: "ansh-menu__text" }, [resolveSlotContent(option.label)])
1070
+ ]
1071
+ );
1072
+ })
1073
+ );
1074
+ }
1075
+ rowIndex++;
1076
+ const index = rowIndex;
1077
+ if ("type" in entry && entry.type === "checkbox") {
1078
+ return h(
1079
+ "div",
1080
+ {
1081
+ key: entry.id ?? `check-${entryIndex}`,
1082
+ ref: (node) => {
1083
+ itemRefs.value[index] = node;
1084
+ },
1085
+ role: "menuitemcheckbox",
1086
+ "aria-checked": entry.checked,
1087
+ "aria-disabled": entry.disabled || void 0,
1088
+ tabindex: index === active.value ? "0" : "-1",
1089
+ class: cx("ansh-menu__item", entry.disabled && "ansh-menu__item--disabled"),
1090
+ onClick: () => activate(index),
1091
+ onMouseenter: () => {
1092
+ if (!entry.disabled) active.value = index;
1093
+ }
1094
+ },
1095
+ [
1096
+ h(
1097
+ "span",
1098
+ {
1099
+ class: cx(
1100
+ "ansh-menu__indicator",
1101
+ "ansh-menu__indicator--check",
1102
+ entry.checked && "ansh-menu__indicator--on"
1103
+ ),
1104
+ "aria-hidden": "true"
1105
+ },
1106
+ [
1107
+ h("svg", { viewBox: "0 0 14 14", width: "10", height: "10" }, [
1108
+ h("path", {
1109
+ d: "M2.5 7.5l3 3 6-6.5",
1110
+ stroke: "currentColor",
1111
+ "stroke-width": "2",
1112
+ "stroke-linecap": "round",
1113
+ "stroke-linejoin": "round",
1114
+ fill: "none"
1115
+ })
1116
+ ])
1117
+ ]
1118
+ ),
1119
+ h("span", { class: "ansh-menu__text" }, [resolveSlotContent(entry.label)]),
1120
+ entry.shortcut ? h("kbd", { class: "ansh-menu__shortcut" }, entry.shortcut) : null
1121
+ ]
1122
+ );
1123
+ }
1124
+ if ("type" in entry && entry.type === "submenu") {
1125
+ return h(Submenu, {
1126
+ key: entry.id ?? `sub-${entryIndex}`,
1127
+ entry,
1128
+ active: index === active.value,
1129
+ open: openSubmenu.value === index,
1130
+ onOpenChange: (next) => {
1131
+ openSubmenu.value = next ? index : null;
1132
+ },
1133
+ onHover: () => {
1134
+ active.value = index;
1135
+ },
1136
+ onCloseAll: () => props.onClose(),
1137
+ onItemRef: (node) => {
1138
+ itemRefs.value[index] = node;
1139
+ }
1140
+ });
1141
+ }
1142
+ const action = entry;
1143
+ return h(
1144
+ "div",
1145
+ {
1146
+ key: action.id ?? `item-${entryIndex}`,
1147
+ ref: (node) => {
1148
+ itemRefs.value[index] = node;
1149
+ },
1150
+ role: "menuitem",
1151
+ "aria-disabled": action.disabled || void 0,
1152
+ tabindex: index === active.value ? "0" : "-1",
1153
+ class: cx(
1154
+ "ansh-menu__item",
1155
+ action.danger && "ansh-menu__item--danger",
1156
+ action.disabled && "ansh-menu__item--disabled"
1157
+ ),
1158
+ onClick: () => activate(index),
1159
+ onMouseenter: () => {
1160
+ if (!action.disabled) active.value = index;
1161
+ }
1162
+ },
1163
+ [
1164
+ action.icon ? h("span", { class: "ansh-menu__icon" }, [resolveSlotContent(action.icon)]) : null,
1165
+ h("span", { class: "ansh-menu__text" }, [resolveSlotContent(action.label)]),
1166
+ action.shortcut ? h("kbd", { class: "ansh-menu__shortcut" }, action.shortcut) : null
1167
+ ]
1168
+ );
1169
+ })
1170
+ );
1171
+ };
1172
+ }
1173
+ });
1174
+ var Submenu = defineComponent({
1175
+ name: "AnshMenuSubmenu",
1176
+ props: {
1177
+ entry: { type: Object, required: true },
1178
+ active: { type: Boolean, required: true },
1179
+ open: { type: Boolean, required: true },
1180
+ onOpenChange: { type: Function, required: true },
1181
+ onHover: { type: Function, required: true },
1182
+ onCloseAll: { type: Function, required: true },
1183
+ /** Reports this submenu's trigger element up so the parent's roving-focus array includes it. */
1184
+ onItemRef: { type: Function, required: true }
1185
+ },
1186
+ setup(props) {
1187
+ const triggerRef = ref(null);
1188
+ const panelRef = ref(null);
1189
+ let timer;
1190
+ const id = useId("submenu");
1191
+ const openRef = computed(() => props.open);
1192
+ const { mounted, state } = usePresence(openRef, panelRef);
1193
+ const { position } = useAnchoredPosition(triggerRef, panelRef, mounted, {
1194
+ placement: "right-start",
1195
+ offset: 2
1196
+ });
1197
+ onBeforeUnmount(() => clearTimeout(timer));
1198
+ return () => h("span", { style: { display: "contents" } }, [
1199
+ h(
1200
+ "div",
1201
+ {
1202
+ ref: (node) => {
1203
+ triggerRef.value = node;
1204
+ props.onItemRef(node);
1205
+ },
1206
+ role: "menuitem",
1207
+ "aria-haspopup": "menu",
1208
+ "aria-expanded": props.open,
1209
+ "aria-controls": props.open ? id : void 0,
1210
+ "aria-disabled": props.entry.disabled || void 0,
1211
+ tabindex: props.active ? "0" : "-1",
1212
+ class: cx(
1213
+ "ansh-menu__item",
1214
+ "ansh-menu__item--submenu",
1215
+ props.entry.disabled && "ansh-menu__item--disabled"
1216
+ ),
1217
+ onMouseenter: () => {
1218
+ props.onHover();
1219
+ clearTimeout(timer);
1220
+ timer = setTimeout(() => props.onOpenChange(true), 120);
1221
+ },
1222
+ onMouseleave: () => {
1223
+ clearTimeout(timer);
1224
+ timer = setTimeout(() => props.onOpenChange(false), 200);
1225
+ },
1226
+ onClick: () => props.onOpenChange(!props.open)
1227
+ },
1228
+ [
1229
+ props.entry.icon ? h("span", { class: "ansh-menu__icon" }, [resolveSlotContent(props.entry.icon)]) : null,
1230
+ h("span", { class: "ansh-menu__text" }, [resolveSlotContent(props.entry.label)]),
1231
+ h("span", { class: "ansh-menu__chevron", "aria-hidden": "true" }, [
1232
+ h("svg", { viewBox: "0 0 12 12", width: "12", height: "12" }, [
1233
+ h("path", {
1234
+ d: "M4.5 2.5L8 6l-3.5 3.5",
1235
+ stroke: "currentColor",
1236
+ "stroke-width": "1.5",
1237
+ fill: "none",
1238
+ "stroke-linecap": "round",
1239
+ "stroke-linejoin": "round"
1240
+ })
1241
+ ])
1242
+ ])
1243
+ ]
1244
+ ),
1245
+ mounted.value ? h(Teleport, { to: "body" }, [
1246
+ h(
1247
+ "div",
1248
+ {
1249
+ onMouseenter: () => clearTimeout(timer),
1250
+ onMouseleave: () => {
1251
+ timer = setTimeout(() => props.onOpenChange(false), 200);
1252
+ },
1253
+ onKeydown: (event) => {
1254
+ if (event.key === "ArrowLeft") {
1255
+ event.preventDefault();
1256
+ event.stopPropagation();
1257
+ props.onOpenChange(false);
1258
+ triggerRef.value?.focus();
1259
+ }
1260
+ }
1261
+ },
1262
+ [
1263
+ h(MenuSurface, {
1264
+ panelRef,
1265
+ id,
1266
+ items: props.entry.items,
1267
+ label: typeof props.entry.label === "string" ? props.entry.label : "Submenu",
1268
+ state: state.value,
1269
+ positionStyle: position.value.style,
1270
+ onClose: props.onCloseAll
1271
+ })
1272
+ ]
1273
+ )
1274
+ ]) : null
1275
+ ]);
1276
+ }
1277
+ });
1278
+
1279
+ export { AlertDialog, ContextMenu, Dialog, DialogFooter, Drawer, DropdownMenu, HoverCard, Popover, PopoverContent, PopoverTrigger, Tooltip, TooltipProvider };
1280
+ //# sourceMappingURL=chunk-2VSROZ2N.js.map
1281
+ //# sourceMappingURL=chunk-2VSROZ2N.js.map