@juxtui/ui 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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/dist/components/avatar/JAvatar.vue.d.ts +26 -0
  4. package/dist/components/badge/JBadge.vue.d.ts +26 -0
  5. package/dist/components/button/JButton.vue.d.ts +37 -0
  6. package/dist/components/card/JCard.vue.d.ts +29 -0
  7. package/dist/components/checkbox/JCheckbox.vue.d.ts +36 -0
  8. package/dist/components/command-palette/JCommandPalette.vue.d.ts +81 -0
  9. package/dist/components/command-palette/search.d.ts +16 -0
  10. package/dist/components/dialog/JDialog.vue.d.ts +53 -0
  11. package/dist/components/dropdown-menu/JDropdownMenu.vue.d.ts +39 -0
  12. package/dist/components/dropdown-menu/JDropdownMenuItem.vue.d.ts +35 -0
  13. package/dist/components/dropdown-menu/JDropdownMenuLabel.vue.d.ts +12 -0
  14. package/dist/components/dropdown-menu/JDropdownMenuSeparator.vue.d.ts +3 -0
  15. package/dist/components/dropdown-menu/JDropdownMenuSub.vue.d.ts +17 -0
  16. package/dist/components/dropdown-menu/JMenuContent.vue.d.ts +44 -0
  17. package/dist/components/dropdown-menu/context.d.ts +17 -0
  18. package/dist/components/field/JFieldShell.vue.d.ts +33 -0
  19. package/dist/components/field/useField.d.ts +24 -0
  20. package/dist/components/input/JInput.vue.d.ts +42 -0
  21. package/dist/components/primitives/JPortal.vue.d.ts +17 -0
  22. package/dist/components/primitives/JSlot.d.ts +8 -0
  23. package/dist/components/select/JSelect.vue.d.ts +53 -0
  24. package/dist/components/switch/JSwitch.vue.d.ts +37 -0
  25. package/dist/components/tabs/JTabs.vue.d.ts +48 -0
  26. package/dist/components/textarea/JTextarea.vue.d.ts +38 -0
  27. package/dist/components/toast/JToastItem.vue.d.ts +13 -0
  28. package/dist/components/toast/JToaster.vue.d.ts +18 -0
  29. package/dist/components/tooltip/JTooltip.vue.d.ts +24 -0
  30. package/dist/components.d.ts +19 -0
  31. package/dist/composables/useFloating.d.ts +28 -0
  32. package/dist/composables/useFocusTrap.d.ts +12 -0
  33. package/dist/composables/useHotkey.d.ts +10 -0
  34. package/dist/composables/useLayer.d.ts +13 -0
  35. package/dist/composables/useRecentCommands.d.ts +19 -0
  36. package/dist/composables/useScrollLock.d.ts +3 -0
  37. package/dist/composables/useToast.d.ts +93 -0
  38. package/dist/composables/useTypeahead.d.ts +8 -0
  39. package/dist/icons.d.ts +18 -0
  40. package/dist/index.d.ts +30 -0
  41. package/dist/index.js +3486 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/nuxt.d.ts +13 -0
  44. package/dist/nuxt.js +61 -0
  45. package/dist/nuxt.js.map +1 -0
  46. package/dist/plugin.d.ts +9 -0
  47. package/dist/style.css +2463 -0
  48. package/dist/tailwind.css +74 -0
  49. package/dist/tokens.css +237 -0
  50. package/dist/utils/dom.d.ts +10 -0
  51. package/package.json +68 -0
package/dist/index.js ADDED
@@ -0,0 +1,3486 @@
1
+ import { Comment, Fragment, Teleport, Text, Transition, TransitionGroup, cloneVNode, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createStaticVNode, createTextVNode, createVNode, defineComponent, h, inject, mergeModels, mergeProps, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, provide, reactive, readonly, ref, renderList, renderSlot, resolveDynamicComponent, shallowRef, toDisplayString, toRef, toValue, unref, useId, useModel, vModelDynamic, vModelText, watch, watchEffect, withCtx, withDirectives, withModifiers } from "vue";
2
+ import { autoUpdate, computePosition, flip, offset, shift, size } from "@floating-ui/dom";
3
+ //#region \0rolldown/runtime.js
4
+ var __defProp = Object.defineProperty;
5
+ var __exportAll = (all, no_symbols) => {
6
+ let target = {};
7
+ for (var name in all) __defProp(target, name, {
8
+ get: all[name],
9
+ enumerable: true
10
+ });
11
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
12
+ return target;
13
+ };
14
+ //#endregion
15
+ //#region src/components/avatar/JAvatar.vue?vue&type=script&setup=true&lang.ts
16
+ var _hoisted_1$19 = [
17
+ "role",
18
+ "aria-label",
19
+ "data-state"
20
+ ];
21
+ var _hoisted_2$14 = { class: "j-avatar__frame" };
22
+ var _hoisted_3$13 = ["src"];
23
+ var _hoisted_4$9 = {
24
+ key: 1,
25
+ class: "j-avatar__fallback",
26
+ "aria-hidden": "true"
27
+ };
28
+ var _hoisted_5$7 = {
29
+ key: 1,
30
+ viewBox: "0 0 16 16",
31
+ fill: "currentColor",
32
+ class: "j-avatar__glyph"
33
+ };
34
+ //#endregion
35
+ //#region src/components/avatar/JAvatar.vue
36
+ var JAvatar_default = /* @__PURE__ */ defineComponent({
37
+ __name: "JAvatar",
38
+ props: {
39
+ src: {},
40
+ alt: {},
41
+ name: {},
42
+ size: { default: "md" },
43
+ shape: { default: "circle" },
44
+ status: {}
45
+ },
46
+ setup(__props) {
47
+ const props = __props;
48
+ const state = ref(props.src ? "loading" : "error");
49
+ const img = ref(null);
50
+ watch(() => props.src, (src) => {
51
+ state.value = src ? "loading" : "error";
52
+ });
53
+ onMounted(() => {
54
+ if (img.value?.complete && img.value.naturalWidth > 0) state.value = "loaded";
55
+ });
56
+ const initials = computed(() => {
57
+ const parts = (props.name ?? "").trim().split(/\s+/).filter(Boolean);
58
+ if (parts.length === 0) return "";
59
+ const first = parts[0];
60
+ const last = parts.length > 1 ? parts[parts.length - 1] : "";
61
+ return (first[0] + (last[0] ?? "")).toUpperCase();
62
+ });
63
+ const statusLabel = {
64
+ online: "Online",
65
+ away: "Away",
66
+ busy: "Busy",
67
+ offline: "Offline"
68
+ };
69
+ const label = computed(() => {
70
+ const who = props.alt ?? props.name;
71
+ if (!who) return void 0;
72
+ return props.status ? `${who} (${statusLabel[props.status]})` : who;
73
+ });
74
+ return (_ctx, _cache) => {
75
+ return openBlock(), createElementBlock("span", {
76
+ class: normalizeClass(["j-avatar", [`j-avatar--${__props.size}`, `j-avatar--${__props.shape}`]]),
77
+ role: label.value ? "img" : void 0,
78
+ "aria-label": label.value,
79
+ "data-state": state.value
80
+ }, [createElementVNode("span", _hoisted_2$14, [__props.src && state.value !== "error" ? (openBlock(), createElementBlock("img", {
81
+ key: 0,
82
+ ref_key: "img",
83
+ ref: img,
84
+ class: "j-avatar__image",
85
+ src: __props.src,
86
+ alt: "",
87
+ onLoad: _cache[0] || (_cache[0] = ($event) => state.value = "loaded"),
88
+ onError: _cache[1] || (_cache[1] = ($event) => state.value = "error")
89
+ }, null, 40, _hoisted_3$13)) : createCommentVNode("", true), state.value !== "loaded" ? (openBlock(), createElementBlock("span", _hoisted_4$9, [renderSlot(_ctx.$slots, "fallback", {}, () => [initials.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode(toDisplayString(initials.value), 1)], 64)) : (openBlock(), createElementBlock("svg", _hoisted_5$7, [..._cache[2] || (_cache[2] = [createElementVNode("circle", {
90
+ cx: "8",
91
+ cy: "6",
92
+ r: "2.75"
93
+ }, null, -1), createElementVNode("path", { d: "M2.75 14c.6-2.6 2.7-4.25 5.25-4.25S12.65 11.4 13.25 14z" }, null, -1)])]))])])) : createCommentVNode("", true)]), __props.status ? (openBlock(), createElementBlock("span", {
94
+ key: 0,
95
+ class: normalizeClass(["j-avatar__status", `j-avatar__status--${__props.status}`]),
96
+ "aria-hidden": "true"
97
+ }, null, 2)) : createCommentVNode("", true)], 10, _hoisted_1$19);
98
+ };
99
+ }
100
+ });
101
+ //#endregion
102
+ //#region src/components/badge/JBadge.vue?vue&type=script&setup=true&lang.ts
103
+ var _hoisted_1$18 = {
104
+ key: 0,
105
+ class: "j-badge__marker",
106
+ "aria-hidden": "true"
107
+ };
108
+ var _hoisted_2$13 = {
109
+ key: 1,
110
+ class: "j-badge__icon"
111
+ };
112
+ //#endregion
113
+ //#region src/components/badge/JBadge.vue
114
+ var JBadge_default = /* @__PURE__ */ defineComponent({
115
+ __name: "JBadge",
116
+ props: {
117
+ variant: { default: "neutral" },
118
+ size: { default: "md" },
119
+ dot: { type: Boolean }
120
+ },
121
+ setup(__props) {
122
+ return (_ctx, _cache) => {
123
+ return openBlock(), createElementBlock("span", { class: normalizeClass(["j-badge", [
124
+ `j-badge--${__props.variant}`,
125
+ `j-badge--${__props.size}`,
126
+ { "j-badge--status": __props.dot }
127
+ ]]) }, [__props.dot ? (openBlock(), createElementBlock("span", _hoisted_1$18)) : _ctx.$slots.leading ? (openBlock(), createElementBlock("span", _hoisted_2$13, [renderSlot(_ctx.$slots, "leading")])) : createCommentVNode("", true), renderSlot(_ctx.$slots, "default")], 2);
128
+ };
129
+ }
130
+ });
131
+ //#endregion
132
+ //#region src/icons.ts
133
+ function icon(name, children, viewBox = "0 0 16 16") {
134
+ const component = (props) => h("svg", {
135
+ "xmlns": "http://www.w3.org/2000/svg",
136
+ viewBox,
137
+ "width": 16,
138
+ "height": 16,
139
+ "fill": "none",
140
+ "stroke": "currentColor",
141
+ "stroke-width": 1.5,
142
+ "stroke-linecap": "round",
143
+ "stroke-linejoin": "round",
144
+ "aria-hidden": "true",
145
+ "focusable": "false",
146
+ ...props
147
+ }, children());
148
+ component.displayName = name;
149
+ return component;
150
+ }
151
+ var IconCheck = icon("IconCheck", () => [h("path", { d: "M3.5 8.5l3 3 6-7" })]);
152
+ icon("IconChevronDown", () => [h("path", { d: "M4.5 6.25L8 9.75l3.5-3.5" })]);
153
+ var IconChevronUpDown = icon("IconChevronUpDown", () => [h("path", { d: "M5.25 6L8 3.25 10.75 6M5.25 10L8 12.75 10.75 10" })]);
154
+ var IconChevronRight = icon("IconChevronRight", () => [h("path", { d: "M6.25 4.5L9.75 8l-3.5 3.5" })]);
155
+ var IconX = icon("IconX", () => [h("path", { d: "M4.5 4.5l7 7M11.5 4.5l-7 7" })]);
156
+ var IconSearch = icon("IconSearch", () => [h("circle", {
157
+ cx: 7,
158
+ cy: 7,
159
+ r: 4.25
160
+ }), h("path", { d: "M10.25 10.25L13 13" })]);
161
+ var IconCircleCheck = icon("IconCircleCheck", () => [h("circle", {
162
+ cx: 8,
163
+ cy: 8,
164
+ r: 6.25
165
+ }), h("path", { d: "M5.5 8.25l1.75 1.75L10.5 6.5" })]);
166
+ var IconCircleX = icon("IconCircleX", () => [h("circle", {
167
+ cx: 8,
168
+ cy: 8,
169
+ r: 6.25
170
+ }), h("path", { d: "M6.25 6.25l3.5 3.5M9.75 6.25l-3.5 3.5" })]);
171
+ var IconAlert = icon("IconAlert", () => [
172
+ h("path", { d: "M7.13 2.75a1 1 0 0 1 1.74 0l5.15 9a1 1 0 0 1-.87 1.5H2.85a1 1 0 0 1-.87-1.5z" }),
173
+ h("path", { d: "M8 6.5v2.5" }),
174
+ h("path", {
175
+ "d": "M8 11.1v.01",
176
+ "stroke-width": 1.75
177
+ })
178
+ ]);
179
+ var IconInfo = icon("IconInfo", () => [
180
+ h("circle", {
181
+ cx: 8,
182
+ cy: 8,
183
+ r: 6.25
184
+ }),
185
+ h("path", { d: "M8 7.5v3.25" }),
186
+ h("path", {
187
+ "d": "M8 5.25v.01",
188
+ "stroke-width": 1.75
189
+ })
190
+ ]);
191
+ icon("IconCornerDownLeft", () => [h("path", { d: "M12.5 3.5v4.25a2 2 0 0 1-2 2h-7" }), h("path", { d: "M6 7l-2.5 2.75L6 12.5" })]);
192
+ /** The loading spinner used across Juxt. A quarter arc over a faint track. */
193
+ var Spinner = (props) => {
194
+ const size = props.size ?? 16;
195
+ return h("svg", {
196
+ "class": "j-spinner",
197
+ "viewBox": "0 0 16 16",
198
+ "width": size,
199
+ "height": size,
200
+ "aria-hidden": "true",
201
+ "focusable": "false"
202
+ }, [h("circle", {
203
+ "class": "j-spinner__track",
204
+ "cx": 8,
205
+ "cy": 8,
206
+ "r": 6,
207
+ "stroke-width": 1.75
208
+ }), h("circle", {
209
+ "cx": 8,
210
+ "cy": 8,
211
+ "r": 6,
212
+ "stroke-width": 1.75,
213
+ "stroke-dasharray": "9.5 28",
214
+ "stroke-dashoffset": 0
215
+ })]);
216
+ };
217
+ Spinner.displayName = "JSpinner";
218
+ //#endregion
219
+ //#region src/components/button/JButton.vue?vue&type=script&setup=true&lang.ts
220
+ var _hoisted_1$17 = { class: "j-button__content" };
221
+ var _hoisted_2$12 = {
222
+ key: 0,
223
+ class: "j-button__icon"
224
+ };
225
+ var _hoisted_3$12 = {
226
+ key: 1,
227
+ class: "j-button__icon"
228
+ };
229
+ var _hoisted_4$8 = {
230
+ key: 0,
231
+ class: "j-button__spinner"
232
+ };
233
+ //#endregion
234
+ //#region src/components/button/JButton.vue
235
+ var JButton_default = /* @__PURE__ */ defineComponent({
236
+ __name: "JButton",
237
+ props: {
238
+ variant: { default: "primary" },
239
+ size: { default: "md" },
240
+ loading: { type: Boolean },
241
+ disabled: { type: Boolean },
242
+ icon: { type: Boolean },
243
+ block: { type: Boolean },
244
+ as: { default: "button" },
245
+ type: { default: "button" }
246
+ },
247
+ setup(__props) {
248
+ const props = __props;
249
+ const isNativeButton = computed(() => props.as === "button");
250
+ const inert = computed(() => props.disabled || props.loading);
251
+ const attrs = computed(() => {
252
+ if (isNativeButton.value) return {
253
+ type: props.type,
254
+ "disabled": props.disabled || void 0,
255
+ "aria-disabled": props.loading || void 0
256
+ };
257
+ return {
258
+ "aria-disabled": inert.value || void 0,
259
+ "tabindex": props.disabled ? -1 : void 0
260
+ };
261
+ });
262
+ function onClick(event) {
263
+ if (inert.value) {
264
+ event.preventDefault();
265
+ event.stopImmediatePropagation();
266
+ }
267
+ }
268
+ return (_ctx, _cache) => {
269
+ return openBlock(), createBlock(resolveDynamicComponent(__props.as), mergeProps(attrs.value, {
270
+ class: ["j-button j-focusable", [
271
+ `j-button--${__props.variant}`,
272
+ `j-button--${__props.size}`,
273
+ {
274
+ "j-button--icon": __props.icon,
275
+ "j-button--block": __props.block,
276
+ "is-loading": __props.loading
277
+ }
278
+ ]],
279
+ "aria-busy": __props.loading || void 0,
280
+ "data-disabled": __props.disabled || void 0,
281
+ onClickCapture: onClick
282
+ }), {
283
+ default: withCtx(() => [createElementVNode("span", _hoisted_1$17, [
284
+ _ctx.$slots.leading ? (openBlock(), createElementBlock("span", _hoisted_2$12, [renderSlot(_ctx.$slots, "leading")])) : createCommentVNode("", true),
285
+ renderSlot(_ctx.$slots, "default"),
286
+ _ctx.$slots.trailing ? (openBlock(), createElementBlock("span", _hoisted_3$12, [renderSlot(_ctx.$slots, "trailing")])) : createCommentVNode("", true)
287
+ ]), createVNode(Transition, { name: "j-button-spinner" }, {
288
+ default: withCtx(() => [__props.loading ? (openBlock(), createElementBlock("span", _hoisted_4$8, [createVNode(unref(Spinner), { size: __props.size === "xs" ? 12 : __props.size === "lg" ? 16 : 14 }, null, 8, ["size"])])) : createCommentVNode("", true)]),
289
+ _: 1
290
+ })]),
291
+ _: 3
292
+ }, 16, [
293
+ "class",
294
+ "aria-busy",
295
+ "data-disabled"
296
+ ]);
297
+ };
298
+ }
299
+ });
300
+ //#endregion
301
+ //#region src/components/card/JCard.vue?vue&type=script&setup=true&lang.ts
302
+ var _hoisted_1$16 = {
303
+ key: 0,
304
+ class: "j-card__header"
305
+ };
306
+ var _hoisted_2$11 = { class: "j-card__heading" };
307
+ var _hoisted_3$11 = {
308
+ key: 0,
309
+ class: "j-card__title"
310
+ };
311
+ var _hoisted_4$7 = {
312
+ key: 1,
313
+ class: "j-card__description"
314
+ };
315
+ var _hoisted_5$6 = {
316
+ key: 0,
317
+ class: "j-card__actions"
318
+ };
319
+ var _hoisted_6$5 = {
320
+ key: 1,
321
+ class: "j-card__body"
322
+ };
323
+ var _hoisted_7$2 = {
324
+ key: 2,
325
+ class: "j-card__footer"
326
+ };
327
+ //#endregion
328
+ //#region src/components/card/JCard.vue
329
+ var JCard_default = /* @__PURE__ */ defineComponent({
330
+ __name: "JCard",
331
+ props: {
332
+ title: {},
333
+ description: {},
334
+ variant: { default: "outline" },
335
+ padding: { default: "md" },
336
+ as: { default: "div" }
337
+ },
338
+ setup(__props) {
339
+ return (_ctx, _cache) => {
340
+ return openBlock(), createBlock(resolveDynamicComponent(__props.as), { class: normalizeClass(["j-card", [`j-card--${__props.variant}`, `j-card--p-${__props.padding}`]]) }, {
341
+ default: withCtx(() => [
342
+ __props.title || __props.description || _ctx.$slots.header || _ctx.$slots.actions ? (openBlock(), createElementBlock("header", _hoisted_1$16, [renderSlot(_ctx.$slots, "header", {}, () => [createElementVNode("div", _hoisted_2$11, [__props.title ? (openBlock(), createElementBlock("h3", _hoisted_3$11, toDisplayString(__props.title), 1)) : createCommentVNode("", true), __props.description ? (openBlock(), createElementBlock("p", _hoisted_4$7, toDisplayString(__props.description), 1)) : createCommentVNode("", true)])]), _ctx.$slots.actions ? (openBlock(), createElementBlock("div", _hoisted_5$6, [renderSlot(_ctx.$slots, "actions")])) : createCommentVNode("", true)])) : createCommentVNode("", true),
343
+ _ctx.$slots.default ? (openBlock(), createElementBlock("div", _hoisted_6$5, [renderSlot(_ctx.$slots, "default")])) : createCommentVNode("", true),
344
+ _ctx.$slots.footer ? (openBlock(), createElementBlock("footer", _hoisted_7$2, [renderSlot(_ctx.$slots, "footer")])) : createCommentVNode("", true)
345
+ ]),
346
+ _: 3
347
+ }, 8, ["class"]);
348
+ };
349
+ }
350
+ });
351
+ //#endregion
352
+ //#region src/components/checkbox/JCheckbox.vue?vue&type=script&setup=true&lang.ts
353
+ var _hoisted_1$15 = ["data-state"];
354
+ var _hoisted_2$10 = { class: "j-checkbox__control" };
355
+ var _hoisted_3$10 = [
356
+ "id",
357
+ "checked",
358
+ "disabled",
359
+ "required",
360
+ "name",
361
+ "value",
362
+ "aria-describedby"
363
+ ];
364
+ var _hoisted_4$6 = {
365
+ key: 0,
366
+ class: "j-checkbox__text"
367
+ };
368
+ var _hoisted_5$5 = ["for"];
369
+ var _hoisted_6$4 = ["id"];
370
+ //#endregion
371
+ //#region src/components/checkbox/JCheckbox.vue
372
+ var JCheckbox_default = /* @__PURE__ */ defineComponent({
373
+ inheritAttrs: false,
374
+ __name: "JCheckbox",
375
+ props: /*@__PURE__*/ mergeModels({
376
+ label: {},
377
+ description: {},
378
+ disabled: { type: Boolean },
379
+ required: { type: Boolean },
380
+ indeterminate: { type: Boolean },
381
+ name: {},
382
+ value: {},
383
+ id: {}
384
+ }, {
385
+ "modelValue": {
386
+ type: Boolean,
387
+ default: false
388
+ },
389
+ "modelModifiers": {}
390
+ }),
391
+ emits: ["update:modelValue"],
392
+ setup(__props, { expose: __expose }) {
393
+ const props = __props;
394
+ const model = useModel(__props, "modelValue");
395
+ const generated = useId();
396
+ const id = computed(() => props.id ?? `j-${generated}`);
397
+ const descriptionId = computed(() => `${id.value}-description`);
398
+ const input = ref(null);
399
+ watchEffect(() => {
400
+ if (input.value) input.value.indeterminate = !!props.indeterminate;
401
+ });
402
+ const state = computed(() => props.indeterminate ? "indeterminate" : model.value ? "checked" : "unchecked");
403
+ function onChange(event) {
404
+ model.value = event.target.checked;
405
+ }
406
+ __expose({
407
+ input,
408
+ focus: () => input.value?.focus()
409
+ });
410
+ return (_ctx, _cache) => {
411
+ return openBlock(), createElementBlock("div", {
412
+ class: normalizeClass(["j-checkbox", [_ctx.$attrs.class, { "is-disabled": __props.disabled }]]),
413
+ style: normalizeStyle(_ctx.$attrs.style),
414
+ "data-state": state.value
415
+ }, [createElementVNode("span", _hoisted_2$10, [createElementVNode("input", mergeProps({
416
+ id: id.value,
417
+ ref_key: "input",
418
+ ref: input
419
+ }, {
420
+ ..._ctx.$attrs,
421
+ class: void 0,
422
+ style: void 0
423
+ }, {
424
+ type: "checkbox",
425
+ class: "j-checkbox__input",
426
+ checked: model.value,
427
+ disabled: __props.disabled,
428
+ required: __props.required,
429
+ name: __props.name,
430
+ value: __props.value,
431
+ "aria-describedby": __props.description || _ctx.$slots.description ? descriptionId.value : void 0,
432
+ onChange
433
+ }), null, 16, _hoisted_3$10), _cache[0] || (_cache[0] = createStaticVNode("<span class=\"j-checkbox__box\" aria-hidden=\"true\"><svg class=\"j-checkbox__check\" viewBox=\"0 0 16 16\" fill=\"none\"><path d=\"M4 8.25l2.75 2.75L12 5.5\" pathLength=\"1\"></path></svg><svg class=\"j-checkbox__dash\" viewBox=\"0 0 16 16\" fill=\"none\"><path d=\"M4.5 8h7\"></path></svg></span>", 1))]), __props.label || _ctx.$slots.default || __props.description || _ctx.$slots.description ? (openBlock(), createElementBlock("span", _hoisted_4$6, [__props.label || _ctx.$slots.default ? (openBlock(), createElementBlock("label", {
434
+ key: 0,
435
+ for: id.value,
436
+ class: "j-checkbox__label"
437
+ }, [renderSlot(_ctx.$slots, "default", {}, () => [createTextVNode(toDisplayString(__props.label), 1)])], 8, _hoisted_5$5)) : createCommentVNode("", true), __props.description || _ctx.$slots.description ? (openBlock(), createElementBlock("span", {
438
+ key: 1,
439
+ id: descriptionId.value,
440
+ class: "j-checkbox__description"
441
+ }, [renderSlot(_ctx.$slots, "description", {}, () => [createTextVNode(toDisplayString(__props.description), 1)])], 8, _hoisted_6$4)) : createCommentVNode("", true)])) : createCommentVNode("", true)], 14, _hoisted_1$15);
442
+ };
443
+ }
444
+ });
445
+ //#endregion
446
+ //#region src/utils/dom.ts
447
+ var isClient = typeof window !== "undefined" && typeof document !== "undefined";
448
+ /** Resolve a template ref that may point at an element or a component instance. */
449
+ function unrefElement(target) {
450
+ const value = unref(target);
451
+ if (!value) return null;
452
+ if (value instanceof Element) return value;
453
+ const el = value.$el;
454
+ if (el && el.nodeType !== Node.ELEMENT_NODE) {
455
+ const next = el.nextSibling;
456
+ return next instanceof HTMLElement ? next : null;
457
+ }
458
+ return el ?? null;
459
+ }
460
+ var FOCUSABLE = [
461
+ "a[href]",
462
+ "area[href]",
463
+ "button:not([disabled])",
464
+ "input:not([disabled]):not([type=\"hidden\"])",
465
+ "select:not([disabled])",
466
+ "textarea:not([disabled])",
467
+ "iframe",
468
+ "audio[controls]",
469
+ "video[controls]",
470
+ "[contenteditable]:not([contenteditable=\"false\"])",
471
+ "[tabindex]"
472
+ ].join(",");
473
+ function isVisible(el) {
474
+ if (el.hidden) return false;
475
+ if (typeof el.checkVisibility === "function") return el.checkVisibility({ visibilityProperty: true });
476
+ for (let node = el; node; node = node.parentElement) {
477
+ const style = getComputedStyle(node);
478
+ if (style.display === "none" || style.visibility === "hidden") return false;
479
+ }
480
+ return true;
481
+ }
482
+ /** Elements that can receive keyboard focus, in DOM order. */
483
+ function getFocusableElements(container) {
484
+ return Array.from(container.querySelectorAll(FOCUSABLE)).filter((el) => el.tabIndex >= 0 && !el.closest("[inert]") && isVisible(el));
485
+ }
486
+ function focusElement(el, options) {
487
+ if (!el) return;
488
+ el.focus({
489
+ preventScroll: true,
490
+ ...options
491
+ });
492
+ }
493
+ var isMac = () => isClient && /mac|iphone|ipad|ipod/i.test(navigator.platform || navigator.userAgent);
494
+ function prefersReducedMotion() {
495
+ return isClient && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
496
+ }
497
+ //#endregion
498
+ //#region src/composables/useFocusTrap.ts
499
+ var stack = [];
500
+ function isTop(trap) {
501
+ return stack[stack.length - 1] === trap;
502
+ }
503
+ var listening$1 = false;
504
+ function onKeydown$1(event) {
505
+ if (event.key !== "Tab" || event.defaultPrevented) return;
506
+ const container = stack[stack.length - 1]?.container.value;
507
+ if (!container) return;
508
+ const focusable = getFocusableElements(container);
509
+ if (focusable.length === 0) {
510
+ event.preventDefault();
511
+ focusElement(container);
512
+ return;
513
+ }
514
+ const first = focusable[0];
515
+ const last = focusable[focusable.length - 1];
516
+ const active = document.activeElement;
517
+ const inside = active ? container.contains(active) : false;
518
+ if (event.shiftKey && (active === first || active === container || !inside)) {
519
+ event.preventDefault();
520
+ focusElement(last);
521
+ } else if (!event.shiftKey && (active === last || !inside)) {
522
+ event.preventDefault();
523
+ focusElement(first);
524
+ }
525
+ }
526
+ function onFocusIn(event) {
527
+ const container = stack[stack.length - 1]?.container.value;
528
+ const target = event.target;
529
+ if (!container || !target || container.contains(target)) return;
530
+ if (target.closest?.("[data-juxt-layer-ignore]")) return;
531
+ const [first] = getFocusableElements(container);
532
+ focusElement(first ?? container);
533
+ }
534
+ function listen$1() {
535
+ if (listening$1 || !isClient) return;
536
+ document.addEventListener("keydown", onKeydown$1);
537
+ document.addEventListener("focusin", onFocusIn);
538
+ listening$1 = true;
539
+ }
540
+ function unlisten$1() {
541
+ if (!listening$1 || stack.length > 0) return;
542
+ document.removeEventListener("keydown", onKeydown$1);
543
+ document.removeEventListener("focusin", onFocusIn);
544
+ listening$1 = false;
545
+ }
546
+ /**
547
+ * Keep keyboard focus inside `container` while `active` is true, and put it
548
+ * back where it came from afterwards.
549
+ */
550
+ function useFocusTrap(container, active, options = {}) {
551
+ const trap = { container };
552
+ let returnTo = null;
553
+ let activeContainer = null;
554
+ async function activate() {
555
+ returnTo = document.activeElement;
556
+ stack.push(trap);
557
+ listen$1();
558
+ await nextTick();
559
+ const el = container.value;
560
+ activeContainer = el;
561
+ if (!el || !isTop(trap)) return;
562
+ if (el.contains(document.activeElement)) return;
563
+ focusElement(options.initialFocus?.() ?? el.querySelector("[autofocus], [data-autofocus]") ?? getFocusableElements(el)[0] ?? el);
564
+ }
565
+ function deactivate() {
566
+ const index = stack.indexOf(trap);
567
+ if (index === -1) return;
568
+ stack.splice(index, 1);
569
+ unlisten$1();
570
+ if (options.restoreFocus === false) return;
571
+ const target = returnTo;
572
+ returnTo = null;
573
+ if (!target || !target.isConnected) return;
574
+ const current = document.activeElement;
575
+ const lost = !current || current === document.body || !current.isConnected || !!activeContainer?.contains(current) || !!container.value?.contains(current);
576
+ activeContainer = null;
577
+ if (lost) focusElement(target);
578
+ }
579
+ if (isClient) {
580
+ watch(active, (value) => {
581
+ if (value) activate();
582
+ else deactivate();
583
+ }, {
584
+ immediate: true,
585
+ flush: "post"
586
+ });
587
+ onBeforeUnmount(deactivate);
588
+ }
589
+ }
590
+ //#endregion
591
+ //#region src/composables/useHotkey.ts
592
+ /**
593
+ * Parse a shortcut such as `mod+k`, `shift+/` or `alt+t`.
594
+ * `mod` is ⌘ on Apple platforms and Ctrl elsewhere.
595
+ */
596
+ function matchesHotkey(event, hotkey) {
597
+ const parts = hotkey.toLowerCase().split("+").map((part) => part.trim());
598
+ const key = parts.pop();
599
+ if (!key) return false;
600
+ const mac = isMac();
601
+ const want = {
602
+ meta: parts.includes("meta") || mac && parts.includes("mod"),
603
+ ctrl: parts.includes("ctrl") || !mac && parts.includes("mod"),
604
+ alt: parts.includes("alt"),
605
+ shift: parts.includes("shift")
606
+ };
607
+ if (event.metaKey !== want.meta || event.ctrlKey !== want.ctrl || event.altKey !== want.alt || event.shiftKey !== want.shift) return false;
608
+ const code = event.code.toLowerCase();
609
+ return event.key.toLowerCase() === key || code === `key${key}` || code === `digit${key}`;
610
+ }
611
+ /** Human-readable parts of a shortcut, e.g. `mod+k` → `['⌘', 'K']`. */
612
+ function formatHotkey(hotkey) {
613
+ const labels = isMac() ? {
614
+ mod: "⌘",
615
+ meta: "⌘",
616
+ ctrl: "⌃",
617
+ alt: "⌥",
618
+ shift: "⇧",
619
+ enter: "↵",
620
+ escape: "Esc"
621
+ } : {
622
+ mod: "Ctrl",
623
+ meta: "Win",
624
+ ctrl: "Ctrl",
625
+ alt: "Alt",
626
+ shift: "Shift",
627
+ enter: "Enter",
628
+ escape: "Esc"
629
+ };
630
+ return hotkey.split("+").map((part) => {
631
+ const lower = part.trim().toLowerCase();
632
+ return labels[lower] ?? (lower.length === 1 ? lower.toUpperCase() : part.trim());
633
+ });
634
+ }
635
+ /** Run `handler` whenever the shortcut is pressed anywhere on the page. */
636
+ function useHotkey(hotkey, handler) {
637
+ function onKeydown(event) {
638
+ const value = toValue(hotkey);
639
+ if (!value || event.repeat || event.isComposing) return;
640
+ if (!matchesHotkey(event, value)) return;
641
+ event.preventDefault();
642
+ handler(event);
643
+ }
644
+ onMounted(() => document.addEventListener("keydown", onKeydown));
645
+ onBeforeUnmount(() => document.removeEventListener("keydown", onKeydown));
646
+ }
647
+ //#endregion
648
+ //#region src/composables/useLayer.ts
649
+ var layers = [];
650
+ var listening = false;
651
+ function top() {
652
+ return layers[layers.length - 1];
653
+ }
654
+ function onKeydown(event) {
655
+ if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
656
+ const layer = top();
657
+ if (!layer?.options.onEscape) return;
658
+ event.preventDefault();
659
+ event.stopPropagation();
660
+ layer.options.onEscape(event);
661
+ }
662
+ function onPointerDown(event) {
663
+ const layer = top();
664
+ if (!layer?.options.onPointerDownOutside) return;
665
+ const target = event.target;
666
+ if (!target) return;
667
+ if (target.closest?.("[data-juxt-layer-ignore]")) return;
668
+ if (!layer.options.elements().some((el) => el?.contains(target))) layer.options.onPointerDownOutside(event);
669
+ }
670
+ function listen() {
671
+ if (listening || !isClient) return;
672
+ document.addEventListener("keydown", onKeydown);
673
+ document.addEventListener("pointerdown", onPointerDown, true);
674
+ listening = true;
675
+ }
676
+ function unlisten() {
677
+ if (!listening || layers.length > 0) return;
678
+ document.removeEventListener("keydown", onKeydown);
679
+ document.removeEventListener("pointerdown", onPointerDown, true);
680
+ listening = false;
681
+ }
682
+ function useLayer(options) {
683
+ const layer = { options };
684
+ const add = () => {
685
+ if (layers.includes(layer)) return;
686
+ layers.push(layer);
687
+ listen();
688
+ };
689
+ const remove = () => {
690
+ const index = layers.indexOf(layer);
691
+ if (index === -1) return;
692
+ layers.splice(index, 1);
693
+ unlisten();
694
+ };
695
+ if (isClient) {
696
+ watch(options.active, (value) => value ? add() : remove(), { immediate: true });
697
+ onBeforeUnmount(remove);
698
+ }
699
+ return { isTopLayer: () => top() === layer };
700
+ }
701
+ //#endregion
702
+ //#region src/composables/useScrollLock.ts
703
+ var locks = 0;
704
+ var saved = null;
705
+ function lock() {
706
+ locks++;
707
+ if (locks > 1) return;
708
+ const body = document.body;
709
+ const scrollbar = window.innerWidth - document.documentElement.clientWidth;
710
+ saved = {
711
+ overflow: body.style.overflow,
712
+ paddingRight: body.style.paddingRight
713
+ };
714
+ body.style.overflow = "hidden";
715
+ if (scrollbar > 0) {
716
+ const current = Number.parseFloat(getComputedStyle(body).paddingRight) || 0;
717
+ body.style.paddingRight = `${current + scrollbar}px`;
718
+ }
719
+ document.documentElement.style.setProperty("--juxt-scrollbar-width", `${scrollbar}px`);
720
+ document.documentElement.dataset.juxtScrollLocked = "";
721
+ }
722
+ function unlock() {
723
+ locks = Math.max(0, locks - 1);
724
+ if (locks > 0 || !saved) return;
725
+ document.body.style.overflow = saved.overflow;
726
+ document.body.style.paddingRight = saved.paddingRight;
727
+ document.documentElement.style.removeProperty("--juxt-scrollbar-width");
728
+ delete document.documentElement.dataset.juxtScrollLocked;
729
+ saved = null;
730
+ }
731
+ /** Prevent the page behind an overlay from scrolling while `active` is true. */
732
+ function useScrollLock(active) {
733
+ if (!isClient) return;
734
+ let held = false;
735
+ const set = (value) => {
736
+ if (value === held) return;
737
+ held = value;
738
+ if (value) lock();
739
+ else unlock();
740
+ };
741
+ watch(active, set, { immediate: true });
742
+ onBeforeUnmount(() => set(false));
743
+ }
744
+ //#endregion
745
+ //#region src/components/command-palette/search.ts
746
+ var NO_MATCH = {
747
+ score: 0,
748
+ ranges: []
749
+ };
750
+ function matchText(query, text) {
751
+ const q = query.trim().toLowerCase();
752
+ if (!q) return {
753
+ score: 1,
754
+ ranges: []
755
+ };
756
+ const t = text.toLowerCase();
757
+ if (!t) return NO_MATCH;
758
+ if (t === q) return {
759
+ score: 1e3,
760
+ ranges: [[0, t.length]]
761
+ };
762
+ if (t.startsWith(q)) return {
763
+ score: 900 - Math.min(t.length - q.length, 100),
764
+ ranges: [[0, q.length]]
765
+ };
766
+ const wordStart = findWordStart(t, q);
767
+ if (wordStart > 0) return {
768
+ score: 700 - Math.min(wordStart, 100),
769
+ ranges: [[wordStart, wordStart + q.length]]
770
+ };
771
+ const index = t.indexOf(q);
772
+ if (index > 0) return {
773
+ score: 500 - Math.min(index, 100),
774
+ ranges: [[index, index + q.length]]
775
+ };
776
+ return subsequence(q, t);
777
+ }
778
+ function findWordStart(t, q) {
779
+ let from = 0;
780
+ while (from < t.length) {
781
+ const index = t.indexOf(q, from);
782
+ if (index === -1) return -1;
783
+ if (index > 0 && /[\s\-_/.:]/.test(t[index - 1])) return index;
784
+ from = index + 1;
785
+ }
786
+ return -1;
787
+ }
788
+ /** Letters in order, not necessarily adjacent — "gtst" finds "Go to settings". */
789
+ function subsequence(q, t) {
790
+ const ranges = [];
791
+ let ti = 0;
792
+ let score = 100;
793
+ let previous = -2;
794
+ for (const char of q) {
795
+ if (char === " ") continue;
796
+ const found = t.indexOf(char, ti);
797
+ if (found === -1) return NO_MATCH;
798
+ if (found === previous + 1 && ranges.length) {
799
+ ranges[ranges.length - 1][1] = found + 1;
800
+ score += 12;
801
+ } else {
802
+ ranges.push([found, found + 1]);
803
+ if (found === 0 || /[\s\-_/.:]/.test(t[found - 1])) score += 8;
804
+ }
805
+ score -= Math.min(found - ti, 10);
806
+ previous = found;
807
+ ti = found + 1;
808
+ }
809
+ return {
810
+ score: Math.max(score, 1),
811
+ ranges
812
+ };
813
+ }
814
+ /** Split text into highlighted and plain runs. */
815
+ function splitByRanges(text, ranges) {
816
+ if (!ranges.length) return [{
817
+ text,
818
+ match: false
819
+ }];
820
+ const parts = [];
821
+ let cursor = 0;
822
+ for (const [start, end] of ranges) {
823
+ if (start > cursor) parts.push({
824
+ text: text.slice(cursor, start),
825
+ match: false
826
+ });
827
+ parts.push({
828
+ text: text.slice(start, end),
829
+ match: true
830
+ });
831
+ cursor = end;
832
+ }
833
+ if (cursor < text.length) parts.push({
834
+ text: text.slice(cursor),
835
+ match: false
836
+ });
837
+ return parts;
838
+ }
839
+ //#endregion
840
+ //#region src/components/primitives/JPortal.vue
841
+ var JPortal_default = /* @__PURE__ */ defineComponent({
842
+ __name: "JPortal",
843
+ props: { to: { default: "body" } },
844
+ setup(__props) {
845
+ /**
846
+ * Internal: teleports its content to `<body>`, but only once mounted.
847
+ *
848
+ * Server-rendered teleports to `body` never make it into the HTML, so a plain
849
+ * `<Teleport>` hydrates against nothing and warns. Rendering nothing until
850
+ * mount keeps server and client output identical — and overlays are closed on
851
+ * first paint anyway.
852
+ */
853
+ const mounted = ref(false);
854
+ onMounted(() => mounted.value = true);
855
+ return (_ctx, _cache) => {
856
+ return mounted.value ? (openBlock(), createBlock(Teleport, {
857
+ key: 0,
858
+ to: __props.to
859
+ }, [renderSlot(_ctx.$slots, "default")], 8, ["to"])) : createCommentVNode("", true);
860
+ };
861
+ }
862
+ });
863
+ //#endregion
864
+ //#region src/components/command-palette/JCommandPalette.vue?vue&type=script&setup=true&lang.ts
865
+ var _hoisted_1$14 = {
866
+ key: 0,
867
+ class: "j-command__overlay",
868
+ "aria-hidden": "true"
869
+ };
870
+ var _hoisted_2$9 = ["aria-label"];
871
+ var _hoisted_3$9 = { class: "j-command__search" };
872
+ var _hoisted_4$5 = [
873
+ "aria-activedescendant",
874
+ "aria-label",
875
+ "placeholder"
876
+ ];
877
+ var _hoisted_5$4 = {
878
+ key: 0,
879
+ class: "j-command__progress",
880
+ "aria-hidden": "true"
881
+ };
882
+ var _hoisted_6$3 = ["aria-label"];
883
+ var _hoisted_7$1 = ["aria-labelledby"];
884
+ var _hoisted_8$1 = ["id"];
885
+ var _hoisted_9$1 = [
886
+ "id",
887
+ "aria-selected",
888
+ "aria-disabled",
889
+ "onPointermove",
890
+ "onClick"
891
+ ];
892
+ var _hoisted_10 = {
893
+ key: 0,
894
+ class: "j-command__item-icon"
895
+ };
896
+ var _hoisted_11 = { class: "j-command__item-text" };
897
+ var _hoisted_12 = { key: 0 };
898
+ var _hoisted_13 = {
899
+ key: 0,
900
+ class: "j-command__item-description"
901
+ };
902
+ var _hoisted_14 = {
903
+ key: 1,
904
+ class: "j-command__item-shortcut",
905
+ "aria-hidden": "true"
906
+ };
907
+ var _hoisted_15 = {
908
+ key: 0,
909
+ class: "j-command__empty",
910
+ role: "presentation"
911
+ };
912
+ var _hoisted_16 = { class: "j-command__empty-title" };
913
+ var _hoisted_17 = {
914
+ key: 0,
915
+ class: "j-command__empty-hint"
916
+ };
917
+ var _hoisted_18 = { class: "j-command__footer" };
918
+ var _hoisted_19 = {
919
+ class: "j-sr-only",
920
+ "aria-live": "polite",
921
+ "aria-atomic": "true"
922
+ };
923
+ //#endregion
924
+ //#region src/components/command-palette/JCommandPalette.vue
925
+ var JCommandPalette_default = /* @__PURE__ */ defineComponent({
926
+ __name: "JCommandPalette",
927
+ props: /*@__PURE__*/ mergeModels({
928
+ groups: {},
929
+ recent: {},
930
+ placeholder: { default: "Type a command or search…" },
931
+ loading: { type: Boolean },
932
+ filter: {
933
+ type: Boolean,
934
+ default: true
935
+ },
936
+ hotkey: {
937
+ type: [String, Boolean],
938
+ default: "mod+k"
939
+ },
940
+ label: { default: "Command palette" },
941
+ emptyText: { default: "No results" }
942
+ }, {
943
+ "open": {
944
+ type: Boolean,
945
+ default: false
946
+ },
947
+ "openModifiers": {},
948
+ "search": { default: "" },
949
+ "searchModifiers": {}
950
+ }),
951
+ emits: /*@__PURE__*/ mergeModels(["select"], ["update:open", "update:search"]),
952
+ setup(__props, { expose: __expose, emit: __emit }) {
953
+ const props = __props;
954
+ const open = useModel(__props, "open");
955
+ const search = useModel(__props, "search");
956
+ const emit = __emit;
957
+ const id = `j-${useId()}`;
958
+ const listboxId = `${id}-listbox`;
959
+ const optionId = (key) => `${id}-option-${key.replace(/[^\w-]/g, "_")}`;
960
+ const sections = computed(() => {
961
+ const query = search.value.trim();
962
+ const toKey = (groupId, item) => `${groupId}:${item.id}`;
963
+ if (!query || !props.filter) {
964
+ const base = [];
965
+ if (!query && props.recent?.length) base.push({
966
+ id: "recent",
967
+ label: "Recent",
968
+ rows: props.recent.map((item) => ({
969
+ key: toKey("recent", item),
970
+ item,
971
+ ranges: [],
972
+ score: 1
973
+ }))
974
+ });
975
+ for (const group of props.groups) if (group.items.length) base.push({
976
+ id: group.id,
977
+ label: group.label,
978
+ rows: group.items.map((item) => ({
979
+ key: toKey(group.id, item),
980
+ item,
981
+ ranges: [],
982
+ score: 1
983
+ }))
984
+ });
985
+ return base;
986
+ }
987
+ const ranked = [];
988
+ for (const group of props.groups) {
989
+ const rows = [];
990
+ for (const item of group.items) {
991
+ const label = matchText(query, item.label);
992
+ let score = label.score;
993
+ if (item.keywords) for (const keyword of item.keywords) score = Math.max(score, matchText(query, keyword).score * .9);
994
+ if (item.description) score = Math.max(score, matchText(query, item.description).score * .5);
995
+ if (score > 0) rows.push({
996
+ key: toKey(group.id, item),
997
+ item,
998
+ ranges: label.ranges,
999
+ score
1000
+ });
1001
+ }
1002
+ if (rows.length) {
1003
+ rows.sort((a, b) => b.score - a.score);
1004
+ ranked.push({
1005
+ id: group.id,
1006
+ label: group.label,
1007
+ rows
1008
+ });
1009
+ }
1010
+ }
1011
+ return ranked.sort((a, b) => b.rows[0].score - a.rows[0].score);
1012
+ });
1013
+ const rows = computed(() => sections.value.flatMap((section) => section.rows));
1014
+ const rowIndex = computed(() => new Map(rows.value.map((row, index) => [row.key, index])));
1015
+ const activeIndex = ref(0);
1016
+ const activeRow = computed(() => rows.value[activeIndex.value]);
1017
+ function firstEnabled(from = 0, delta = 1) {
1018
+ const count = rows.value.length;
1019
+ for (let i = 0; i < count; i++) {
1020
+ const index = (from + i * delta + count * 2) % count;
1021
+ if (!rows.value[index].item.disabled) return index;
1022
+ }
1023
+ return -1;
1024
+ }
1025
+ watch(rows, () => activeIndex.value = Math.max(0, firstEnabled()));
1026
+ const panel = ref(null);
1027
+ const input = ref(null);
1028
+ const list = ref(null);
1029
+ useHotkey(() => props.hotkey, () => open.value = !open.value);
1030
+ useLayer({
1031
+ active: open,
1032
+ elements: () => [panel.value],
1033
+ onEscape: () => close()
1034
+ });
1035
+ useFocusTrap(panel, open, { initialFocus: () => input.value });
1036
+ useScrollLock(open);
1037
+ watch(open, (value) => {
1038
+ if (!value) return;
1039
+ search.value = "";
1040
+ activeIndex.value = Math.max(0, firstEnabled());
1041
+ nextTick(() => list.value?.scrollTo({ top: 0 }));
1042
+ });
1043
+ function close() {
1044
+ open.value = false;
1045
+ }
1046
+ let pressedOutside = false;
1047
+ function onPointerDown(event) {
1048
+ pressedOutside = event.target === event.currentTarget;
1049
+ }
1050
+ function onClick(event) {
1051
+ if (pressedOutside && event.target === event.currentTarget) close();
1052
+ pressedOutside = false;
1053
+ }
1054
+ function choose(row) {
1055
+ if (!row || row.item.disabled) return;
1056
+ row.item.onSelect?.(row.item);
1057
+ emit("select", row.item);
1058
+ close();
1059
+ }
1060
+ function setActive(index) {
1061
+ if (index < 0) return;
1062
+ activeIndex.value = index;
1063
+ const row = rows.value[index];
1064
+ if (!row) return;
1065
+ nextTick(() => {
1066
+ const el = document.getElementById(optionId(row.key));
1067
+ if (index === 0) list.value?.scrollTo({ top: 0 });
1068
+ else el?.scrollIntoView({ block: "nearest" });
1069
+ });
1070
+ }
1071
+ function move(delta, distance = 1) {
1072
+ const count = rows.value.length;
1073
+ if (!count) return;
1074
+ let index = activeIndex.value;
1075
+ for (let step = 0; step < distance; step++) {
1076
+ const next = firstEnabled((index + delta + count) % count, delta);
1077
+ if (next === -1) return;
1078
+ if (distance > 1 && (delta === 1 && next < index || delta === -1 && next > index)) break;
1079
+ index = next;
1080
+ }
1081
+ setActive(index);
1082
+ }
1083
+ function onKeydown(event) {
1084
+ if (event.isComposing) return;
1085
+ const ctrl = event.ctrlKey && !event.metaKey && !event.altKey;
1086
+ const key = event.key;
1087
+ if (key === "ArrowDown" || ctrl && key === "n") move(1);
1088
+ else if (key === "ArrowUp" || ctrl && key === "p") move(-1);
1089
+ else if (key === "PageDown") move(1, 5);
1090
+ else if (key === "PageUp") move(-1, 5);
1091
+ else if (key === "Enter") choose(activeRow.value);
1092
+ else return;
1093
+ event.preventDefault();
1094
+ }
1095
+ function onRowPointerMove(index) {
1096
+ if (activeIndex.value !== index && !rows.value[index]?.item.disabled) activeIndex.value = index;
1097
+ }
1098
+ const announcement = ref("");
1099
+ let announceTimer;
1100
+ watch([rows, () => props.loading], () => {
1101
+ clearTimeout(announceTimer);
1102
+ announceTimer = setTimeout(() => {
1103
+ if (!open.value) return;
1104
+ if (props.loading) announcement.value = "Loading results";
1105
+ else announcement.value = rows.value.length === 1 ? "1 result" : `${rows.value.length} results`;
1106
+ }, 350);
1107
+ });
1108
+ const mounted = ref(false);
1109
+ onMounted(() => mounted.value = true);
1110
+ const keys = (hotkey) => mounted.value ? formatHotkey(hotkey) : [];
1111
+ __expose({
1112
+ close,
1113
+ focus: () => input.value?.focus()
1114
+ });
1115
+ return (_ctx, _cache) => {
1116
+ return openBlock(), createBlock(JPortal_default, null, {
1117
+ default: withCtx(() => [createVNode(Transition, { name: "j-command-overlay" }, {
1118
+ default: withCtx(() => [open.value ? (openBlock(), createElementBlock("div", _hoisted_1$14)) : createCommentVNode("", true)]),
1119
+ _: 1
1120
+ }), createVNode(Transition, {
1121
+ name: "j-command",
1122
+ duration: {
1123
+ enter: 180,
1124
+ leave: 110
1125
+ }
1126
+ }, {
1127
+ default: withCtx(() => [open.value ? (openBlock(), createElementBlock("div", {
1128
+ key: 0,
1129
+ class: "j-command__positioner",
1130
+ onPointerdown: onPointerDown,
1131
+ onClick
1132
+ }, [createElementVNode("div", {
1133
+ ref_key: "panel",
1134
+ ref: panel,
1135
+ role: "dialog",
1136
+ "aria-modal": "true",
1137
+ "aria-label": __props.label,
1138
+ class: "j-command"
1139
+ }, [
1140
+ createElementVNode("div", _hoisted_3$9, [
1141
+ createVNode(unref(IconSearch), { class: "j-command__search-icon" }),
1142
+ withDirectives(createElementVNode("input", {
1143
+ ref_key: "input",
1144
+ ref: input,
1145
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => search.value = $event),
1146
+ class: "j-command__input",
1147
+ type: "text",
1148
+ role: "combobox",
1149
+ "aria-expanded": "true",
1150
+ "aria-autocomplete": "list",
1151
+ "aria-controls": listboxId,
1152
+ "aria-activedescendant": activeRow.value ? optionId(activeRow.value.key) : void 0,
1153
+ "aria-label": __props.label,
1154
+ placeholder: __props.placeholder,
1155
+ autocomplete: "off",
1156
+ autocorrect: "off",
1157
+ autocapitalize: "off",
1158
+ spellcheck: "false",
1159
+ enterkeyhint: "go",
1160
+ onKeydown
1161
+ }, null, 40, _hoisted_4$5), [[vModelText, search.value]]),
1162
+ createElementVNode("button", {
1163
+ type: "button",
1164
+ class: "j-command__esc j-kbd",
1165
+ tabindex: "-1",
1166
+ "aria-label": "Close",
1167
+ onClick: close
1168
+ }, " Esc "),
1169
+ __props.loading ? (openBlock(), createElementBlock("span", _hoisted_5$4)) : createCommentVNode("", true)
1170
+ ]),
1171
+ createElementVNode("div", {
1172
+ id: listboxId,
1173
+ ref_key: "list",
1174
+ ref: list,
1175
+ role: "listbox",
1176
+ "aria-label": __props.label,
1177
+ class: "j-command__list",
1178
+ onPointerdown: _cache[1] || (_cache[1] = withModifiers(() => {}, ["prevent"]))
1179
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(sections.value, (section) => {
1180
+ return openBlock(), createElementBlock("div", {
1181
+ key: section.id,
1182
+ role: "group",
1183
+ class: "j-command__group",
1184
+ "aria-labelledby": section.label ? `${id}-group-${section.id}` : void 0
1185
+ }, [section.label ? (openBlock(), createElementBlock("div", {
1186
+ key: 0,
1187
+ id: `${id}-group-${section.id}`,
1188
+ class: "j-command__group-label",
1189
+ role: "presentation"
1190
+ }, toDisplayString(section.label), 9, _hoisted_8$1)) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(section.rows, (row) => {
1191
+ return openBlock(), createElementBlock("div", {
1192
+ id: optionId(row.key),
1193
+ key: row.key,
1194
+ role: "option",
1195
+ class: normalizeClass(["j-command__item", { "is-active": row.key === activeRow.value?.key }]),
1196
+ "aria-selected": row.key === activeRow.value?.key,
1197
+ "aria-disabled": row.item.disabled || void 0,
1198
+ onPointermove: ($event) => onRowPointerMove(rowIndex.value.get(row.key)),
1199
+ onClick: ($event) => choose(row)
1200
+ }, [renderSlot(_ctx.$slots, "item", {
1201
+ item: row.item,
1202
+ active: row.key === activeRow.value?.key
1203
+ }, () => [
1204
+ row.item.icon ? (openBlock(), createElementBlock("span", _hoisted_10, [(openBlock(), createBlock(resolveDynamicComponent(row.item.icon)))])) : createCommentVNode("", true),
1205
+ createElementVNode("span", _hoisted_11, [createElementVNode("span", { class: normalizeClass(["j-command__item-label", { "is-searching": row.ranges.length }]) }, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(splitByRanges)(row.item.label, row.ranges), (part, i) => {
1206
+ return openBlock(), createElementBlock(Fragment, { key: i }, [part.match ? (openBlock(), createElementBlock("mark", _hoisted_12, toDisplayString(part.text), 1)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(part.text), 1)], 64))], 64);
1207
+ }), 128))], 2), row.item.description ? (openBlock(), createElementBlock("span", _hoisted_13, toDisplayString(row.item.description), 1)) : createCommentVNode("", true)]),
1208
+ row.item.shortcut ? (openBlock(), createElementBlock("span", _hoisted_14, [(openBlock(true), createElementBlock(Fragment, null, renderList(keys(row.item.shortcut), (key) => {
1209
+ return openBlock(), createElementBlock("kbd", {
1210
+ key,
1211
+ class: "j-kbd"
1212
+ }, toDisplayString(key), 1);
1213
+ }), 128))])) : createCommentVNode("", true)
1214
+ ])], 42, _hoisted_9$1);
1215
+ }), 128))], 8, _hoisted_7$1);
1216
+ }), 128)), !rows.value.length ? (openBlock(), createElementBlock("div", _hoisted_15, [__props.loading ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createVNode(unref(Spinner), { size: 16 }), _cache[2] || (_cache[2] = createElementVNode("span", null, "Searching…", -1))], 64)) : renderSlot(_ctx.$slots, "empty", { search: search.value }, () => [createElementVNode("span", _hoisted_16, toDisplayString(__props.emptyText), 1), search.value ? (openBlock(), createElementBlock("span", _hoisted_17, "Nothing matches “" + toDisplayString(search.value) + "”. Try fewer words.", 1)) : createCommentVNode("", true)], void 0, 1)])) : createCommentVNode("", true)], 40, _hoisted_6$3),
1217
+ createElementVNode("div", _hoisted_18, [renderSlot(_ctx.$slots, "footer", {}, () => [
1218
+ _cache[3] || (_cache[3] = createElementVNode("span", { class: "j-command__hint" }, [
1219
+ createElementVNode("kbd", { class: "j-kbd" }, "↑"),
1220
+ createElementVNode("kbd", { class: "j-kbd" }, "↓"),
1221
+ createTextVNode(" Navigate")
1222
+ ], -1)),
1223
+ _cache[4] || (_cache[4] = createElementVNode("span", { class: "j-command__hint" }, [createElementVNode("kbd", { class: "j-kbd" }, "↵"), createTextVNode(" Select")], -1)),
1224
+ _cache[5] || (_cache[5] = createElementVNode("span", { class: "j-command__hint" }, [createElementVNode("kbd", { class: "j-kbd" }, "Esc"), createTextVNode(" Close")], -1))
1225
+ ])]),
1226
+ createElementVNode("span", _hoisted_19, toDisplayString(announcement.value), 1)
1227
+ ], 8, _hoisted_2$9)], 32)) : createCommentVNode("", true)]),
1228
+ _: 3
1229
+ })]),
1230
+ _: 3
1231
+ });
1232
+ };
1233
+ }
1234
+ });
1235
+ //#endregion
1236
+ //#region src/components/dialog/JDialog.vue?vue&type=script&setup=true&lang.ts
1237
+ var _hoisted_1$13 = {
1238
+ key: 0,
1239
+ class: "j-dialog__overlay",
1240
+ "aria-hidden": "true"
1241
+ };
1242
+ var _hoisted_2$8 = [
1243
+ "aria-labelledby",
1244
+ "aria-label",
1245
+ "aria-describedby"
1246
+ ];
1247
+ var _hoisted_3$8 = {
1248
+ key: 0,
1249
+ class: "j-dialog__header"
1250
+ };
1251
+ var _hoisted_4$4 = {
1252
+ key: 1,
1253
+ class: "j-dialog__body"
1254
+ };
1255
+ var _hoisted_5$3 = {
1256
+ key: 2,
1257
+ class: "j-dialog__footer"
1258
+ };
1259
+ //#endregion
1260
+ //#region src/components/dialog/JDialog.vue
1261
+ var JDialog_default = /* @__PURE__ */ defineComponent({
1262
+ __name: "JDialog",
1263
+ props: /*@__PURE__*/ mergeModels({
1264
+ title: {},
1265
+ description: {},
1266
+ size: { default: "md" },
1267
+ closeOnOverlay: {
1268
+ type: Boolean,
1269
+ default: true
1270
+ },
1271
+ closeOnEscape: {
1272
+ type: Boolean,
1273
+ default: true
1274
+ },
1275
+ hideClose: { type: Boolean },
1276
+ ariaLabel: {}
1277
+ }, {
1278
+ "open": {
1279
+ type: Boolean,
1280
+ default: false
1281
+ },
1282
+ "openModifiers": {}
1283
+ }),
1284
+ emits: /*@__PURE__*/ mergeModels(["afterClose"], ["update:open"]),
1285
+ setup(__props, { expose: __expose, emit: __emit }) {
1286
+ const props = __props;
1287
+ const open = useModel(__props, "open");
1288
+ const emit = __emit;
1289
+ const id = useId();
1290
+ const titleId = `j-${id}-title`;
1291
+ const descriptionId = `j-${id}-description`;
1292
+ const panel = ref(null);
1293
+ const nudging = ref(false);
1294
+ function close() {
1295
+ open.value = false;
1296
+ }
1297
+ function nudge() {
1298
+ if (prefersReducedMotion()) return;
1299
+ nudging.value = false;
1300
+ requestAnimationFrame(() => nudging.value = true);
1301
+ }
1302
+ const { isTopLayer } = useLayer({
1303
+ active: open,
1304
+ elements: () => [panel.value],
1305
+ onEscape: () => props.closeOnEscape ? close() : nudge()
1306
+ });
1307
+ useFocusTrap(panel, open, { initialFocus: () => panel.value?.querySelector("[autofocus], [data-autofocus]") ?? panel.value?.querySelector(".j-dialog__body :is(input:not([type=hidden]), textarea, select, [role=combobox]):not(:disabled)") ?? panel.value });
1308
+ useScrollLock(open);
1309
+ let pressedOutside = false;
1310
+ function onPointerDown(event) {
1311
+ pressedOutside = event.target === event.currentTarget && isTopLayer();
1312
+ }
1313
+ function onClick(event) {
1314
+ const outside = pressedOutside && event.target === event.currentTarget;
1315
+ pressedOutside = false;
1316
+ if (!outside) return;
1317
+ if (props.closeOnOverlay) close();
1318
+ else nudge();
1319
+ }
1320
+ __expose({ close });
1321
+ return (_ctx, _cache) => {
1322
+ return openBlock(), createBlock(JPortal_default, null, {
1323
+ default: withCtx(() => [createVNode(Transition, { name: "j-dialog-overlay" }, {
1324
+ default: withCtx(() => [open.value ? (openBlock(), createElementBlock("div", _hoisted_1$13)) : createCommentVNode("", true)]),
1325
+ _: 1
1326
+ }), createVNode(Transition, {
1327
+ name: "j-dialog",
1328
+ duration: {
1329
+ enter: 240,
1330
+ leave: 150
1331
+ },
1332
+ onAfterLeave: _cache[1] || (_cache[1] = ($event) => emit("afterClose"))
1333
+ }, {
1334
+ default: withCtx(() => [open.value ? (openBlock(), createElementBlock("div", {
1335
+ key: 0,
1336
+ class: "j-dialog__positioner",
1337
+ onPointerdown: onPointerDown,
1338
+ onClick
1339
+ }, [createElementVNode("div", {
1340
+ ref_key: "panel",
1341
+ ref: panel,
1342
+ role: "dialog",
1343
+ "aria-modal": "true",
1344
+ tabindex: "-1",
1345
+ class: normalizeClass(["j-dialog__panel", [`j-dialog__panel--${__props.size}`, { "is-nudging": nudging.value }]]),
1346
+ "aria-labelledby": __props.title || _ctx.$slots.header ? titleId : void 0,
1347
+ "aria-label": __props.ariaLabel,
1348
+ "aria-describedby": __props.description ? descriptionId : void 0,
1349
+ onAnimationend: _cache[0] || (_cache[0] = ($event) => nudging.value = false)
1350
+ }, [
1351
+ __props.title || __props.description || _ctx.$slots.header ? (openBlock(), createElementBlock("header", _hoisted_3$8, [renderSlot(_ctx.$slots, "header", {
1352
+ titleId,
1353
+ descriptionId
1354
+ }, () => [createElementVNode("h2", {
1355
+ id: titleId,
1356
+ class: "j-dialog__title"
1357
+ }, toDisplayString(__props.title), 1), __props.description ? (openBlock(), createElementBlock("p", {
1358
+ key: 0,
1359
+ id: descriptionId,
1360
+ class: "j-dialog__description"
1361
+ }, toDisplayString(__props.description), 1)) : createCommentVNode("", true)])])) : createCommentVNode("", true),
1362
+ _ctx.$slots.default ? (openBlock(), createElementBlock("div", _hoisted_4$4, [renderSlot(_ctx.$slots, "default", { close })])) : createCommentVNode("", true),
1363
+ _ctx.$slots.footer ? (openBlock(), createElementBlock("footer", _hoisted_5$3, [renderSlot(_ctx.$slots, "footer", { close })])) : createCommentVNode("", true),
1364
+ !__props.hideClose ? (openBlock(), createElementBlock("button", {
1365
+ key: 3,
1366
+ type: "button",
1367
+ class: "j-dialog__close j-focusable",
1368
+ "aria-label": "Close",
1369
+ onClick: close
1370
+ }, [createVNode(unref(IconX))])) : createCommentVNode("", true)
1371
+ ], 42, _hoisted_2$8)], 32)) : createCommentVNode("", true)]),
1372
+ _: 3
1373
+ })]),
1374
+ _: 3
1375
+ });
1376
+ };
1377
+ }
1378
+ });
1379
+ //#endregion
1380
+ //#region src/components/primitives/JSlot.ts
1381
+ function firstElement(nodes) {
1382
+ if (!nodes) return void 0;
1383
+ for (const node of nodes) {
1384
+ if (node.type === Comment) continue;
1385
+ if (node.type === Fragment && Array.isArray(node.children)) {
1386
+ const found = firstElement(node.children);
1387
+ if (found) return found;
1388
+ continue;
1389
+ }
1390
+ if (node.type === Text) {
1391
+ if (typeof node.children === "string" && node.children.trim() === "") continue;
1392
+ return h("span", null, node.children);
1393
+ }
1394
+ return node;
1395
+ }
1396
+ }
1397
+ /**
1398
+ * Internal: renders its first child element and merges its own attributes
1399
+ * (ARIA, listeners, ids) onto it — no wrapper element.
1400
+ */
1401
+ var JSlot = defineComponent({
1402
+ name: "JSlot",
1403
+ inheritAttrs: false,
1404
+ setup(_, { slots, attrs }) {
1405
+ return () => {
1406
+ const child = firstElement(slots.default?.());
1407
+ if (!child) return null;
1408
+ const { id, ...rest } = attrs;
1409
+ return cloneVNode(child, child.props?.id ? rest : {
1410
+ id,
1411
+ ...rest
1412
+ }, true);
1413
+ };
1414
+ }
1415
+ });
1416
+ //#endregion
1417
+ //#region src/components/dropdown-menu/context.ts
1418
+ var MENU_ROOT = Symbol("JDropdownMenu");
1419
+ var MENU_LEVEL = Symbol("JDropdownMenuLevel");
1420
+ function getMenuItems(content) {
1421
+ if (!content) return [];
1422
+ return Array.from(content.querySelectorAll("[role^=\"menuitem\"]:not([aria-disabled=\"true\"])"));
1423
+ }
1424
+ //#endregion
1425
+ //#region src/composables/useFloating.ts
1426
+ var ORIGIN = {
1427
+ top: "bottom",
1428
+ bottom: "top",
1429
+ left: "right",
1430
+ right: "left"
1431
+ };
1432
+ /**
1433
+ * Position a floating element next to a reference element and keep it there
1434
+ * through scroll and resize. Coordinates are written to `left`/`top` so
1435
+ * `transform` stays free for enter/leave motion.
1436
+ */
1437
+ function useFloating(reference, floating, options = {}) {
1438
+ const read = () => typeof options === "function" ? options() : options;
1439
+ const x = ref(0);
1440
+ const y = ref(0);
1441
+ const placement = ref(read().placement ?? "bottom-start");
1442
+ const availableHeight = ref();
1443
+ const referenceWidth = ref();
1444
+ const stop = shallowRef();
1445
+ async function update() {
1446
+ const referenceEl = reference.value;
1447
+ const floatingEl = floating.value;
1448
+ if (!referenceEl || !floatingEl) return;
1449
+ const opts = read();
1450
+ const padding = opts.padding ?? 8;
1451
+ const result = await computePosition(referenceEl, floatingEl, {
1452
+ strategy: "fixed",
1453
+ placement: opts.placement ?? "bottom-start",
1454
+ middleware: [
1455
+ offset(opts.offset ?? 6),
1456
+ flip({ padding }),
1457
+ shift({ padding }),
1458
+ size({
1459
+ padding,
1460
+ apply({ availableHeight: height, rects }) {
1461
+ availableHeight.value = Math.max(120, Math.floor(height));
1462
+ referenceWidth.value = rects.reference.width;
1463
+ }
1464
+ })
1465
+ ]
1466
+ });
1467
+ x.value = Math.round(result.x);
1468
+ y.value = Math.round(result.y);
1469
+ placement.value = result.placement;
1470
+ }
1471
+ watch([reference, floating], ([referenceEl, floatingEl]) => {
1472
+ stop.value?.();
1473
+ stop.value = void 0;
1474
+ if (referenceEl && floatingEl) stop.value = autoUpdate(referenceEl, floatingEl, update);
1475
+ }, { flush: "post" });
1476
+ onBeforeUnmount(() => stop.value?.());
1477
+ const side = computed(() => placement.value.split("-")[0]);
1478
+ return {
1479
+ x,
1480
+ y,
1481
+ placement,
1482
+ side,
1483
+ styles: computed(() => {
1484
+ const align = placement.value.split("-")[1];
1485
+ const cross = side.value === "top" || side.value === "bottom" ? align === "start" ? "left" : align === "end" ? "right" : "center" : align === "start" ? "top" : align === "end" ? "bottom" : "center";
1486
+ const origin = side.value === "top" || side.value === "bottom" ? `${cross} ${ORIGIN[side.value]}` : `${ORIGIN[side.value]} ${cross}`;
1487
+ const dir = side.value === "top" ? 1 : side.value === "bottom" ? -1 : 0;
1488
+ return {
1489
+ "position": "fixed",
1490
+ "left": `${x.value}px`,
1491
+ "top": `${y.value}px`,
1492
+ "transformOrigin": origin,
1493
+ "minWidth": read().matchWidth && referenceWidth.value ? `${referenceWidth.value}px` : void 0,
1494
+ "--j-available-height": availableHeight.value ? `${availableHeight.value}px` : void 0,
1495
+ "--j-pop-dir": String(dir),
1496
+ "--j-pop-x": String(side.value === "left" ? 1 : side.value === "right" ? -1 : 0)
1497
+ };
1498
+ }),
1499
+ update
1500
+ };
1501
+ }
1502
+ //#endregion
1503
+ //#region src/composables/useTypeahead.ts
1504
+ /**
1505
+ * Type-to-jump for lists. Characters typed in quick succession form a query;
1506
+ * repeating the same letter cycles through matches.
1507
+ */
1508
+ function useTypeahead(timeout = 500) {
1509
+ let query = "";
1510
+ let timer;
1511
+ function search(key, items, getLabel, currentIndex) {
1512
+ if (key.length !== 1 || key === " " && query === "") return -1;
1513
+ clearTimeout(timer);
1514
+ timer = setTimeout(() => query = "", timeout);
1515
+ query += key.toLowerCase();
1516
+ const repeated = query.length > 1 && [...query].every((char) => char === query[0]);
1517
+ const needle = repeated ? query[0] : query;
1518
+ const start = repeated || query.length === 1 ? currentIndex + 1 : currentIndex;
1519
+ for (let i = 0; i < items.length; i++) {
1520
+ const index = (start + i + items.length) % items.length;
1521
+ if (getLabel(items[index]).trim().toLowerCase().startsWith(needle)) return index;
1522
+ }
1523
+ return -1;
1524
+ }
1525
+ const isSearching = () => query.length > 0;
1526
+ return {
1527
+ search,
1528
+ isSearching
1529
+ };
1530
+ }
1531
+ //#endregion
1532
+ //#region src/components/dropdown-menu/JMenuContent.vue?vue&type=script&setup=true&lang.ts
1533
+ var _hoisted_1$12 = ["id", "aria-labelledby"];
1534
+ //#endregion
1535
+ //#region src/components/dropdown-menu/JMenuContent.vue
1536
+ var JMenuContent_default = /* @__PURE__ */ defineComponent({
1537
+ __name: "JMenuContent",
1538
+ props: {
1539
+ open: { type: Boolean },
1540
+ reference: {},
1541
+ id: {},
1542
+ labelledby: {},
1543
+ placement: {},
1544
+ offset: {},
1545
+ initialFocus: {},
1546
+ isSub: { type: Boolean }
1547
+ },
1548
+ emits: [
1549
+ "escape",
1550
+ "outside",
1551
+ "back",
1552
+ "pointerenter"
1553
+ ],
1554
+ setup(__props, { expose: __expose, emit: __emit }) {
1555
+ /**
1556
+ * Internal: the floating list shared by the root menu and submenus.
1557
+ * Owns positioning, keyboard navigation, typeahead and the layer.
1558
+ */
1559
+ const props = __props;
1560
+ const emit = __emit;
1561
+ const root = inject(MENU_ROOT);
1562
+ const content = ref(null);
1563
+ const { styles } = useFloating(toRef(props, "reference"), content, () => ({
1564
+ placement: props.placement,
1565
+ offset: props.offset
1566
+ }));
1567
+ const typeahead = useTypeahead();
1568
+ useLayer({
1569
+ active: toRef(props, "open"),
1570
+ elements: () => [content.value, props.reference],
1571
+ onEscape: () => emit("escape"),
1572
+ onPointerDownOutside: (event) => emit("outside", event)
1573
+ });
1574
+ const openSub = ref(null);
1575
+ let subTimer;
1576
+ provide(MENU_LEVEL, {
1577
+ content,
1578
+ openSub,
1579
+ setOpenSub: (id) => {
1580
+ clearTimeout(subTimer);
1581
+ openSub.value = id;
1582
+ },
1583
+ scheduleSubClose: () => {
1584
+ if (!openSub.value) return;
1585
+ clearTimeout(subTimer);
1586
+ subTimer = setTimeout(() => openSub.value = null, 180);
1587
+ },
1588
+ cancelSubClose: () => clearTimeout(subTimer)
1589
+ });
1590
+ watch(() => props.open, async (value) => {
1591
+ if (!value) {
1592
+ openSub.value = null;
1593
+ return;
1594
+ }
1595
+ if (props.initialFocus === "none") return;
1596
+ await nextTick();
1597
+ const items = getMenuItems(content.value);
1598
+ if (props.initialFocus === "first") focusElement(items[0] ?? content.value);
1599
+ else if (props.initialFocus === "last") focusElement(items[items.length - 1] ?? content.value);
1600
+ else focusElement(content.value);
1601
+ }, { flush: "post" });
1602
+ function move(delta) {
1603
+ const items = getMenuItems(content.value);
1604
+ if (items.length === 0) return;
1605
+ const current = items.indexOf(document.activeElement);
1606
+ let next;
1607
+ if (delta === "first") next = 0;
1608
+ else if (delta === "last") next = items.length - 1;
1609
+ else if (current === -1) next = delta === 1 ? 0 : items.length - 1;
1610
+ else next = (current + delta + items.length) % items.length;
1611
+ focusElement(items[next]);
1612
+ }
1613
+ function onKeydown(event) {
1614
+ const { key } = event;
1615
+ switch (key) {
1616
+ case "ArrowDown":
1617
+ event.preventDefault();
1618
+ move(1);
1619
+ break;
1620
+ case "ArrowUp":
1621
+ event.preventDefault();
1622
+ move(-1);
1623
+ break;
1624
+ case "Home":
1625
+ case "PageUp":
1626
+ event.preventDefault();
1627
+ move("first");
1628
+ break;
1629
+ case "End":
1630
+ case "PageDown":
1631
+ event.preventDefault();
1632
+ move("last");
1633
+ break;
1634
+ case "ArrowLeft":
1635
+ if (props.isSub) {
1636
+ event.preventDefault();
1637
+ emit("back");
1638
+ }
1639
+ break;
1640
+ case "Tab":
1641
+ event.preventDefault();
1642
+ root.close(true);
1643
+ break;
1644
+ case "Enter":
1645
+ case " ": {
1646
+ const active = document.activeElement;
1647
+ if (active && active !== content.value && content.value?.contains(active)) {
1648
+ event.preventDefault();
1649
+ active.click();
1650
+ }
1651
+ break;
1652
+ }
1653
+ default: if (key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {
1654
+ const items = getMenuItems(content.value);
1655
+ const index = typeahead.search(key, items, (item) => item.textContent ?? "", items.indexOf(document.activeElement));
1656
+ if (index >= 0) focusElement(items[index]);
1657
+ }
1658
+ }
1659
+ }
1660
+ __expose({ content });
1661
+ return (_ctx, _cache) => {
1662
+ return openBlock(), createBlock(JPortal_default, null, {
1663
+ default: withCtx(() => [createVNode(Transition, { name: "j-pop" }, {
1664
+ default: withCtx(() => [__props.open ? (openBlock(), createElementBlock("div", {
1665
+ key: 0,
1666
+ id: __props.id,
1667
+ ref_key: "content",
1668
+ ref: content,
1669
+ role: "menu",
1670
+ tabindex: "-1",
1671
+ class: normalizeClass(["j-menu j-popover-surface", { "j-menu--sub": __props.isSub }]),
1672
+ style: normalizeStyle(unref(styles)),
1673
+ "aria-labelledby": __props.labelledby,
1674
+ "aria-orientation": "vertical",
1675
+ onKeydown,
1676
+ onPointerenter: _cache[0] || (_cache[0] = ($event) => emit("pointerenter"))
1677
+ }, [renderSlot(_ctx.$slots, "default")], 46, _hoisted_1$12)) : createCommentVNode("", true)]),
1678
+ _: 3
1679
+ })]),
1680
+ _: 3
1681
+ });
1682
+ };
1683
+ }
1684
+ });
1685
+ //#endregion
1686
+ //#region src/components/dropdown-menu/JDropdownMenu.vue
1687
+ var JDropdownMenu_default = /* @__PURE__ */ defineComponent({
1688
+ __name: "JDropdownMenu",
1689
+ props: /*@__PURE__*/ mergeModels({
1690
+ placement: { default: "bottom-start" },
1691
+ offset: { default: 6 },
1692
+ disabled: { type: Boolean }
1693
+ }, {
1694
+ "open": {
1695
+ type: Boolean,
1696
+ default: false
1697
+ },
1698
+ "openModifiers": {}
1699
+ }),
1700
+ emits: ["update:open"],
1701
+ setup(__props, { expose: __expose }) {
1702
+ const props = __props;
1703
+ const open = useModel(__props, "open");
1704
+ const id = useId();
1705
+ const triggerId = `j-${id}-trigger`;
1706
+ const menuId = `j-${id}-menu`;
1707
+ const triggerRef = ref();
1708
+ const triggerEl = computed(() => unrefElement(triggerRef.value));
1709
+ const initialFocus = ref("content");
1710
+ function show(focus) {
1711
+ if (props.disabled) return;
1712
+ initialFocus.value = focus;
1713
+ open.value = true;
1714
+ }
1715
+ function close(restoreFocus = true) {
1716
+ if (!open.value) return;
1717
+ open.value = false;
1718
+ if (restoreFocus) nextTick(() => focusElement(triggerEl.value));
1719
+ }
1720
+ provide(MENU_ROOT, { close });
1721
+ function onClick(event) {
1722
+ if (open.value) return close(false);
1723
+ show(event.detail === 0 ? "first" : "content");
1724
+ }
1725
+ function onKeydown(event) {
1726
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
1727
+ event.preventDefault();
1728
+ show(event.key === "ArrowDown" ? "first" : "last");
1729
+ }
1730
+ }
1731
+ const triggerProps = computed(() => ({
1732
+ "id": triggerId,
1733
+ "aria-haspopup": "menu",
1734
+ "aria-expanded": open.value,
1735
+ "aria-controls": open.value ? menuId : void 0,
1736
+ "data-state": open.value ? "open" : "closed",
1737
+ "onClick": onClick,
1738
+ "onKeydown": onKeydown
1739
+ }));
1740
+ __expose({
1741
+ open: () => show("content"),
1742
+ close
1743
+ });
1744
+ return (_ctx, _cache) => {
1745
+ return openBlock(), createElementBlock(Fragment, null, [createVNode(unref(JSlot), mergeProps({
1746
+ ref_key: "triggerRef",
1747
+ ref: triggerRef
1748
+ }, triggerProps.value), {
1749
+ default: withCtx(() => [renderSlot(_ctx.$slots, "trigger", { open: open.value })]),
1750
+ _: 3
1751
+ }, 16), createVNode(JMenuContent_default, {
1752
+ id: menuId,
1753
+ open: open.value,
1754
+ reference: triggerEl.value,
1755
+ labelledby: triggerEl.value?.id || triggerId,
1756
+ placement: __props.placement,
1757
+ offset: __props.offset,
1758
+ "initial-focus": initialFocus.value,
1759
+ onEscape: _cache[0] || (_cache[0] = ($event) => close(true)),
1760
+ onOutside: _cache[1] || (_cache[1] = ($event) => close(false))
1761
+ }, {
1762
+ default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
1763
+ _: 3
1764
+ }, 8, [
1765
+ "open",
1766
+ "reference",
1767
+ "labelledby",
1768
+ "placement",
1769
+ "offset",
1770
+ "initial-focus"
1771
+ ])], 64);
1772
+ };
1773
+ }
1774
+ });
1775
+ //#endregion
1776
+ //#region src/components/dropdown-menu/JDropdownMenuItem.vue?vue&type=script&setup=true&lang.ts
1777
+ var _hoisted_1$11 = {
1778
+ key: 0,
1779
+ class: "j-menu__icon"
1780
+ };
1781
+ var _hoisted_2$7 = { class: "j-menu__text" };
1782
+ var _hoisted_3$7 = {
1783
+ key: 1,
1784
+ class: "j-menu__shortcut"
1785
+ };
1786
+ //#endregion
1787
+ //#region src/components/dropdown-menu/JDropdownMenuItem.vue
1788
+ var JDropdownMenuItem_default = /* @__PURE__ */ defineComponent({
1789
+ __name: "JDropdownMenuItem",
1790
+ props: {
1791
+ disabled: { type: Boolean },
1792
+ destructive: { type: Boolean },
1793
+ shortcut: {},
1794
+ as: { default: "div" },
1795
+ checked: {
1796
+ type: Boolean,
1797
+ default: void 0
1798
+ }
1799
+ },
1800
+ emits: ["select"],
1801
+ setup(__props, { emit: __emit }) {
1802
+ const props = __props;
1803
+ const emit = __emit;
1804
+ const root = inject(MENU_ROOT);
1805
+ const level = inject(MENU_LEVEL);
1806
+ const el = ref(null);
1807
+ function onClick(event) {
1808
+ if (props.disabled) {
1809
+ event.preventDefault();
1810
+ return;
1811
+ }
1812
+ const selectEvent = new Event("select", { cancelable: true });
1813
+ emit("select", selectEvent);
1814
+ if (!selectEvent.defaultPrevented) root.close(true);
1815
+ }
1816
+ function resolve(target) {
1817
+ const value = target;
1818
+ return value instanceof HTMLElement ? value : value?.$el ?? null;
1819
+ }
1820
+ function onPointerMove() {
1821
+ level.scheduleSubClose();
1822
+ const node = resolve(el.value);
1823
+ if (props.disabled || !node || document.activeElement === node) return;
1824
+ focusElement(node);
1825
+ }
1826
+ function onPointerLeave() {
1827
+ const node = resolve(el.value);
1828
+ if (node && document.activeElement === node) focusElement(level.content.value);
1829
+ }
1830
+ return (_ctx, _cache) => {
1831
+ return openBlock(), createBlock(resolveDynamicComponent(__props.as), {
1832
+ ref_key: "el",
1833
+ ref: el,
1834
+ role: __props.checked === void 0 ? "menuitem" : "menuitemradio",
1835
+ "aria-checked": __props.checked === void 0 ? void 0 : __props.checked,
1836
+ tabindex: "-1",
1837
+ class: normalizeClass(["j-menu__item", { "j-menu__item--destructive": __props.destructive }]),
1838
+ "aria-disabled": __props.disabled || void 0,
1839
+ onClick,
1840
+ onPointermove: onPointerMove,
1841
+ onPointerleave: onPointerLeave
1842
+ }, {
1843
+ default: withCtx(() => [
1844
+ _ctx.$slots.leading ? (openBlock(), createElementBlock("span", _hoisted_1$11, [renderSlot(_ctx.$slots, "leading")])) : createCommentVNode("", true),
1845
+ createElementVNode("span", _hoisted_2$7, [renderSlot(_ctx.$slots, "default")]),
1846
+ __props.shortcut ? (openBlock(), createElementBlock("span", _hoisted_3$7, toDisplayString(__props.shortcut), 1)) : createCommentVNode("", true),
1847
+ __props.checked !== void 0 ? (openBlock(), createBlock(unref(IconCheck), {
1848
+ key: 2,
1849
+ class: normalizeClass(["j-menu__check", { "is-checked": __props.checked }])
1850
+ }, null, 8, ["class"])) : createCommentVNode("", true)
1851
+ ]),
1852
+ _: 3
1853
+ }, 40, [
1854
+ "role",
1855
+ "aria-checked",
1856
+ "class",
1857
+ "aria-disabled"
1858
+ ]);
1859
+ };
1860
+ }
1861
+ });
1862
+ //#endregion
1863
+ //#region src/components/dropdown-menu/JDropdownMenuLabel.vue?vue&type=script&setup=true&lang.ts
1864
+ var _hoisted_1$10 = {
1865
+ class: "j-menu__label",
1866
+ role: "presentation"
1867
+ };
1868
+ //#endregion
1869
+ //#region src/components/dropdown-menu/JDropdownMenuLabel.vue
1870
+ var JDropdownMenuLabel_default = /* @__PURE__ */ defineComponent({
1871
+ __name: "JDropdownMenuLabel",
1872
+ setup(__props) {
1873
+ return (_ctx, _cache) => {
1874
+ return openBlock(), createElementBlock("div", _hoisted_1$10, [renderSlot(_ctx.$slots, "default")]);
1875
+ };
1876
+ }
1877
+ });
1878
+ //#endregion
1879
+ //#region \0plugin-vue:export-helper
1880
+ var _plugin_vue_export_helper_default = (sfc, props) => {
1881
+ const target = sfc.__vccOpts || sfc;
1882
+ for (const [key, val] of props) target[key] = val;
1883
+ return target;
1884
+ };
1885
+ //#endregion
1886
+ //#region src/components/dropdown-menu/JDropdownMenuSeparator.vue
1887
+ var _sfc_main = {};
1888
+ var _hoisted_1$9 = {
1889
+ role: "separator",
1890
+ "aria-orientation": "horizontal",
1891
+ class: "j-menu__separator"
1892
+ };
1893
+ function _sfc_render(_ctx, _cache) {
1894
+ return openBlock(), createElementBlock("div", _hoisted_1$9);
1895
+ }
1896
+ var JDropdownMenuSeparator_default = /*#__PURE__*/ _plugin_vue_export_helper_default(_sfc_main, [["render", _sfc_render]]);
1897
+ //#endregion
1898
+ //#region src/components/dropdown-menu/JDropdownMenuSub.vue?vue&type=script&setup=true&lang.ts
1899
+ var _hoisted_1$8 = [
1900
+ "aria-expanded",
1901
+ "aria-controls",
1902
+ "aria-disabled"
1903
+ ];
1904
+ var _hoisted_2$6 = {
1905
+ key: 0,
1906
+ class: "j-menu__icon"
1907
+ };
1908
+ var _hoisted_3$6 = { class: "j-menu__text" };
1909
+ //#endregion
1910
+ //#region src/components/dropdown-menu/JDropdownMenuSub.vue
1911
+ var JDropdownMenuSub_default = /* @__PURE__ */ defineComponent({
1912
+ __name: "JDropdownMenuSub",
1913
+ props: {
1914
+ label: {},
1915
+ disabled: { type: Boolean }
1916
+ },
1917
+ setup(__props) {
1918
+ const props = __props;
1919
+ const root = inject(MENU_ROOT);
1920
+ const level = inject(MENU_LEVEL);
1921
+ const id = useId();
1922
+ const itemId = `j-${id}-subtrigger`;
1923
+ const menuId = `j-${id}-submenu`;
1924
+ const item = ref(null);
1925
+ const sub = ref(null);
1926
+ const initialFocus = ref("none");
1927
+ const isOpen = computed(() => level.openSub.value === menuId);
1928
+ let openTimer;
1929
+ function openSub(focus) {
1930
+ if (props.disabled) return;
1931
+ initialFocus.value = focus;
1932
+ level.setOpenSub(menuId);
1933
+ }
1934
+ function closeSub(returnFocus) {
1935
+ if (level.openSub.value === menuId) level.setOpenSub(null);
1936
+ if (returnFocus) nextTick(() => focusElement(item.value));
1937
+ }
1938
+ function onPointerMove() {
1939
+ level.cancelSubClose();
1940
+ if (props.disabled) return;
1941
+ if (document.activeElement !== item.value) focusElement(item.value);
1942
+ if (isOpen.value) return;
1943
+ clearTimeout(openTimer);
1944
+ openTimer = setTimeout(() => openSub("none"), 90);
1945
+ }
1946
+ function onPointerLeave() {
1947
+ clearTimeout(openTimer);
1948
+ }
1949
+ function onKeydown(event) {
1950
+ if (event.key === "ArrowRight" || event.key === "Enter" || event.key === " ") {
1951
+ event.preventDefault();
1952
+ event.stopPropagation();
1953
+ if (isOpen.value) focusElement(getMenuItems(sub.value?.content ?? null)[0]);
1954
+ else openSub("first");
1955
+ }
1956
+ }
1957
+ function onClick() {
1958
+ openSub("first");
1959
+ }
1960
+ function onOutside(event) {
1961
+ const target = event.target;
1962
+ if (level.content.value?.contains(target)) closeSub(false);
1963
+ else root.close(false);
1964
+ }
1965
+ return (_ctx, _cache) => {
1966
+ return openBlock(), createElementBlock(Fragment, null, [createElementVNode("div", {
1967
+ id: itemId,
1968
+ ref_key: "item",
1969
+ ref: item,
1970
+ role: "menuitem",
1971
+ tabindex: "-1",
1972
+ class: "j-menu__item",
1973
+ "aria-haspopup": "menu",
1974
+ "aria-expanded": isOpen.value,
1975
+ "aria-controls": isOpen.value ? menuId : void 0,
1976
+ "aria-disabled": __props.disabled || void 0,
1977
+ onPointermove: onPointerMove,
1978
+ onPointerleave: onPointerLeave,
1979
+ onKeydown,
1980
+ onClick
1981
+ }, [
1982
+ _ctx.$slots.leading ? (openBlock(), createElementBlock("span", _hoisted_2$6, [renderSlot(_ctx.$slots, "leading")])) : createCommentVNode("", true),
1983
+ createElementVNode("span", _hoisted_3$6, toDisplayString(__props.label), 1),
1984
+ createVNode(unref(IconChevronRight), { class: "j-menu__chevron" })
1985
+ ], 40, _hoisted_1$8), createVNode(JMenuContent_default, {
1986
+ id: menuId,
1987
+ ref_key: "sub",
1988
+ ref: sub,
1989
+ open: isOpen.value,
1990
+ reference: item.value,
1991
+ labelledby: itemId,
1992
+ placement: "right-start",
1993
+ offset: {
1994
+ mainAxis: 2,
1995
+ crossAxis: -5
1996
+ },
1997
+ "initial-focus": initialFocus.value,
1998
+ "is-sub": "",
1999
+ onEscape: _cache[0] || (_cache[0] = ($event) => closeSub(true)),
2000
+ onBack: _cache[1] || (_cache[1] = ($event) => closeSub(true)),
2001
+ onOutside,
2002
+ onPointerenter: _cache[2] || (_cache[2] = ($event) => unref(level).cancelSubClose())
2003
+ }, {
2004
+ default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
2005
+ _: 3
2006
+ }, 8, [
2007
+ "open",
2008
+ "reference",
2009
+ "initial-focus"
2010
+ ])], 64);
2011
+ };
2012
+ }
2013
+ });
2014
+ //#endregion
2015
+ //#region src/components/field/JFieldShell.vue?vue&type=script&setup=true&lang.ts
2016
+ var _hoisted_1$7 = {
2017
+ key: 0,
2018
+ class: "j-field__required",
2019
+ "aria-hidden": "true"
2020
+ };
2021
+ var _hoisted_2$5 = { class: "j-field__messages" };
2022
+ var _hoisted_3$5 = ["id"];
2023
+ var _hoisted_4$3 = ["id"];
2024
+ //#endregion
2025
+ //#region src/components/field/JFieldShell.vue
2026
+ var JFieldShell_default = /* @__PURE__ */ defineComponent({
2027
+ __name: "JFieldShell",
2028
+ props: {
2029
+ id: {},
2030
+ labelId: {},
2031
+ descriptionId: {},
2032
+ errorId: {},
2033
+ label: {},
2034
+ description: {},
2035
+ errorMessage: {},
2036
+ showDescription: { type: Boolean },
2037
+ required: { type: Boolean },
2038
+ disabled: { type: Boolean },
2039
+ labelFor: {}
2040
+ },
2041
+ setup(__props) {
2042
+ /**
2043
+ * Internal: lays out label, control, description and error for form fields.
2044
+ * Not exported — every field component composes it.
2045
+ */
2046
+ return (_ctx, _cache) => {
2047
+ return openBlock(), createElementBlock("div", { class: normalizeClass(["j-field", { "is-disabled": __props.disabled }]) }, [
2048
+ __props.label || _ctx.$slots.label ? (openBlock(), createBlock(resolveDynamicComponent(__props.labelFor === null ? "span" : "label"), {
2049
+ key: 0,
2050
+ id: __props.labelId,
2051
+ class: "j-field__label",
2052
+ for: __props.labelFor === null ? void 0 : __props.labelFor ?? __props.id
2053
+ }, {
2054
+ default: withCtx(() => [renderSlot(_ctx.$slots, "label", {}, () => [createTextVNode(toDisplayString(__props.label), 1)]), __props.required ? (openBlock(), createElementBlock("span", _hoisted_1$7, "*")) : createCommentVNode("", true)]),
2055
+ _: 3
2056
+ }, 8, ["id", "for"])) : createCommentVNode("", true),
2057
+ renderSlot(_ctx.$slots, "default"),
2058
+ createElementVNode("div", _hoisted_2$5, [createVNode(Transition, {
2059
+ name: "j-field-message",
2060
+ mode: "out-in"
2061
+ }, {
2062
+ default: withCtx(() => [__props.errorMessage ? (openBlock(), createElementBlock("p", {
2063
+ id: __props.errorId,
2064
+ key: __props.errorMessage,
2065
+ class: "j-field__error"
2066
+ }, toDisplayString(__props.errorMessage), 9, _hoisted_3$5)) : __props.showDescription ? (openBlock(), createElementBlock("p", {
2067
+ key: 1,
2068
+ id: __props.descriptionId,
2069
+ class: "j-field__description"
2070
+ }, toDisplayString(__props.description), 9, _hoisted_4$3)) : createCommentVNode("", true)]),
2071
+ _: 1
2072
+ })])
2073
+ ], 2);
2074
+ };
2075
+ }
2076
+ });
2077
+ //#endregion
2078
+ //#region src/components/field/useField.ts
2079
+ /** Ids and ARIA wiring shared by every form control. */
2080
+ function useField(props) {
2081
+ const generated = useId();
2082
+ const p = () => toValue(props);
2083
+ const id = computed(() => p().id ?? `j-${generated}`);
2084
+ const labelId = computed(() => `${id.value}-label`);
2085
+ const descriptionId = computed(() => `${id.value}-description`);
2086
+ const errorId = computed(() => `${id.value}-error`);
2087
+ const invalid = computed(() => !!p().error);
2088
+ const errorMessage = computed(() => typeof p().error === "string" ? p().error : "");
2089
+ return {
2090
+ id,
2091
+ labelId,
2092
+ descriptionId,
2093
+ errorId,
2094
+ invalid,
2095
+ errorMessage,
2096
+ showDescription: computed(() => !!p().description && !errorMessage.value),
2097
+ describedBy: computed(() => {
2098
+ const ids = [];
2099
+ if (errorMessage.value) ids.push(errorId.value);
2100
+ else if (p().description) ids.push(descriptionId.value);
2101
+ return ids.length ? ids.join(" ") : void 0;
2102
+ })
2103
+ };
2104
+ }
2105
+ //#endregion
2106
+ //#region src/components/input/JInput.vue?vue&type=script&setup=true&lang.ts
2107
+ var _hoisted_1$6 = {
2108
+ key: 0,
2109
+ class: "j-control__adornment j-control__adornment--leading"
2110
+ };
2111
+ var _hoisted_2$4 = [
2112
+ "id",
2113
+ "type",
2114
+ "name",
2115
+ "placeholder",
2116
+ "disabled",
2117
+ "readonly",
2118
+ "required",
2119
+ "autocomplete",
2120
+ "aria-invalid",
2121
+ "aria-describedby"
2122
+ ];
2123
+ var _hoisted_3$4 = {
2124
+ key: 1,
2125
+ class: "j-control__adornment j-control__adornment--trailing"
2126
+ };
2127
+ //#endregion
2128
+ //#region src/components/input/JInput.vue
2129
+ var JInput_default = /* @__PURE__ */ defineComponent({
2130
+ inheritAttrs: false,
2131
+ __name: "JInput",
2132
+ props: /*@__PURE__*/ mergeModels({
2133
+ type: { default: "text" },
2134
+ placeholder: {},
2135
+ size: { default: "md" },
2136
+ readonly: { type: Boolean },
2137
+ name: {},
2138
+ autocomplete: {},
2139
+ label: {},
2140
+ description: {},
2141
+ error: {
2142
+ type: [String, Boolean],
2143
+ default: void 0
2144
+ },
2145
+ required: { type: Boolean },
2146
+ disabled: { type: Boolean },
2147
+ id: {}
2148
+ }, {
2149
+ "modelValue": { default: "" },
2150
+ "modelModifiers": {}
2151
+ }),
2152
+ emits: ["update:modelValue"],
2153
+ setup(__props, { expose: __expose }) {
2154
+ const props = __props;
2155
+ const model = useModel(__props, "modelValue");
2156
+ const field = useField(props);
2157
+ const input = ref(null);
2158
+ function onFramePointerDown(event) {
2159
+ if (event.target === input.value || props.disabled) return;
2160
+ if (event.target.closest("button, a, input, select, textarea, [tabindex]")) return;
2161
+ event.preventDefault();
2162
+ input.value?.focus();
2163
+ }
2164
+ __expose({
2165
+ /** The underlying `<input>` element. */
2166
+ input,
2167
+ focus: () => input.value?.focus(),
2168
+ select: () => input.value?.select()
2169
+ });
2170
+ return (_ctx, _cache) => {
2171
+ return openBlock(), createBlock(JFieldShell_default, {
2172
+ id: unref(field).id.value,
2173
+ class: normalizeClass(_ctx.$attrs.class),
2174
+ style: normalizeStyle(_ctx.$attrs.style),
2175
+ "label-id": unref(field).labelId.value,
2176
+ "description-id": unref(field).descriptionId.value,
2177
+ "error-id": unref(field).errorId.value,
2178
+ label: __props.label,
2179
+ description: __props.description,
2180
+ "error-message": unref(field).errorMessage.value,
2181
+ "show-description": unref(field).showDescription.value,
2182
+ required: __props.required,
2183
+ disabled: __props.disabled
2184
+ }, createSlots({
2185
+ default: withCtx(() => [createElementVNode("div", {
2186
+ class: normalizeClass(["j-control j-input", [`j-control--${__props.size}`, {
2187
+ "is-invalid": unref(field).invalid.value,
2188
+ "is-disabled": __props.disabled,
2189
+ "is-readonly": __props.readonly
2190
+ }]]),
2191
+ onPointerdown: onFramePointerDown
2192
+ }, [
2193
+ _ctx.$slots.leading ? (openBlock(), createElementBlock("span", _hoisted_1$6, [renderSlot(_ctx.$slots, "leading")])) : createCommentVNode("", true),
2194
+ withDirectives(createElementVNode("input", mergeProps({
2195
+ id: unref(field).id.value,
2196
+ ref_key: "input",
2197
+ ref: input,
2198
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => model.value = $event)
2199
+ }, {
2200
+ ..._ctx.$attrs,
2201
+ class: void 0,
2202
+ style: void 0
2203
+ }, {
2204
+ class: "j-input__native",
2205
+ type: __props.type,
2206
+ name: __props.name,
2207
+ placeholder: __props.placeholder,
2208
+ disabled: __props.disabled,
2209
+ readonly: __props.readonly,
2210
+ required: __props.required,
2211
+ autocomplete: __props.autocomplete,
2212
+ "aria-invalid": unref(field).invalid.value || void 0,
2213
+ "aria-describedby": unref(field).describedBy.value
2214
+ }), null, 16, _hoisted_2$4), [[vModelDynamic, model.value]]),
2215
+ _ctx.$slots.trailing ? (openBlock(), createElementBlock("span", _hoisted_3$4, [renderSlot(_ctx.$slots, "trailing")])) : createCommentVNode("", true)
2216
+ ], 34)]),
2217
+ _: 2
2218
+ }, [_ctx.$slots.label ? {
2219
+ name: "label",
2220
+ fn: withCtx(() => [renderSlot(_ctx.$slots, "label")]),
2221
+ key: "0"
2222
+ } : void 0]), 1032, [
2223
+ "id",
2224
+ "class",
2225
+ "style",
2226
+ "label-id",
2227
+ "description-id",
2228
+ "error-id",
2229
+ "label",
2230
+ "description",
2231
+ "error-message",
2232
+ "show-description",
2233
+ "required",
2234
+ "disabled"
2235
+ ]);
2236
+ };
2237
+ }
2238
+ });
2239
+ //#endregion
2240
+ //#region src/components/select/JSelect.vue?vue&type=script&setup=true&lang.ts
2241
+ var _hoisted_1$5 = [
2242
+ "id",
2243
+ "aria-expanded",
2244
+ "aria-controls",
2245
+ "aria-activedescendant",
2246
+ "aria-labelledby",
2247
+ "aria-describedby",
2248
+ "aria-invalid",
2249
+ "aria-required",
2250
+ "disabled"
2251
+ ];
2252
+ var _hoisted_2$3 = ["id"];
2253
+ var _hoisted_3$3 = ["name", "value"];
2254
+ var _hoisted_4$2 = ["id", "aria-labelledby"];
2255
+ var _hoisted_5$2 = [
2256
+ "id",
2257
+ "aria-selected",
2258
+ "aria-disabled",
2259
+ "onPointermove",
2260
+ "onClick"
2261
+ ];
2262
+ var _hoisted_6$2 = { class: "j-select__option-text" };
2263
+ var _hoisted_7 = { class: "j-select__option-label" };
2264
+ var _hoisted_8 = {
2265
+ key: 0,
2266
+ class: "j-select__option-description"
2267
+ };
2268
+ var _hoisted_9 = {
2269
+ key: 0,
2270
+ class: "j-select__empty",
2271
+ role: "presentation"
2272
+ };
2273
+ //#endregion
2274
+ //#region src/components/select/JSelect.vue
2275
+ var JSelect_default = /* @__PURE__ */ defineComponent({
2276
+ inheritAttrs: false,
2277
+ __name: "JSelect",
2278
+ props: /*@__PURE__*/ mergeModels({
2279
+ options: {},
2280
+ placeholder: { default: "Select…" },
2281
+ size: { default: "md" },
2282
+ name: {},
2283
+ label: {},
2284
+ description: {},
2285
+ error: {
2286
+ type: [String, Boolean],
2287
+ default: void 0
2288
+ },
2289
+ required: { type: Boolean },
2290
+ disabled: { type: Boolean },
2291
+ id: {}
2292
+ }, {
2293
+ "modelValue": { default: null },
2294
+ "modelModifiers": {}
2295
+ }),
2296
+ emits: /*@__PURE__*/ mergeModels(["open", "close"], ["update:modelValue"]),
2297
+ setup(__props, { expose: __expose, emit: __emit }) {
2298
+ const props = __props;
2299
+ const model = useModel(__props, "modelValue");
2300
+ const emit = __emit;
2301
+ const field = useField(props);
2302
+ const listboxId = computed(() => `${field.id.value}-listbox`);
2303
+ const valueId = computed(() => `${field.id.value}-value`);
2304
+ const optionId = (index) => `${field.id.value}-option-${index}`;
2305
+ const normalized = computed(() => props.options.map((option) => typeof option === "object" && option !== null ? option : {
2306
+ value: option,
2307
+ label: String(option)
2308
+ }));
2309
+ const query = ref("");
2310
+ const visibleOptions = computed(() => {
2311
+ const q = query.value.trim().toLowerCase();
2312
+ if (!q) return normalized.value;
2313
+ return normalized.value.filter((option) => option.label.toLowerCase().includes(q));
2314
+ });
2315
+ const selected = computed(() => normalized.value.find((option) => option.value === model.value));
2316
+ const open = ref(false);
2317
+ const activeIndex = ref(-1);
2318
+ const trigger = ref(null);
2319
+ const listbox = ref(null);
2320
+ const pointerActive = ref(false);
2321
+ const { styles } = useFloating(trigger, listbox, {
2322
+ placement: "bottom-start",
2323
+ offset: 4,
2324
+ matchWidth: true
2325
+ });
2326
+ const typeahead = useTypeahead();
2327
+ useLayer({
2328
+ active: open,
2329
+ elements: () => [trigger.value, listbox.value],
2330
+ onEscape: () => close(),
2331
+ onPointerDownOutside: () => close()
2332
+ });
2333
+ const isEnabled = (index) => {
2334
+ const option = visibleOptions.value[index];
2335
+ return !!option && !option.disabled;
2336
+ };
2337
+ function step(from, delta) {
2338
+ const count = visibleOptions.value.length;
2339
+ for (let i = from + delta; i >= 0 && i < count; i += delta) if (isEnabled(i)) return i;
2340
+ return from;
2341
+ }
2342
+ const first = () => step(-1, 1);
2343
+ const last = () => step(visibleOptions.value.length, -1);
2344
+ function setActive(index, scroll = true) {
2345
+ activeIndex.value = index;
2346
+ if (!scroll) return;
2347
+ nextTick(() => {
2348
+ document.getElementById(optionId(index))?.scrollIntoView({ block: "nearest" });
2349
+ });
2350
+ }
2351
+ function openList(focus = "selected") {
2352
+ if (props.disabled || open.value) return;
2353
+ open.value = true;
2354
+ emit("open");
2355
+ const selectedIndex = visibleOptions.value.findIndex((option) => option.value === model.value);
2356
+ setActive(focus === "last" ? last() : focus === "first" ? first() : selectedIndex >= 0 ? selectedIndex : first());
2357
+ }
2358
+ function close() {
2359
+ if (!open.value) return;
2360
+ open.value = false;
2361
+ pointerActive.value = false;
2362
+ emit("close");
2363
+ }
2364
+ function commit(index) {
2365
+ const option = visibleOptions.value[index];
2366
+ if (!option || option.disabled) return;
2367
+ model.value = option.value;
2368
+ close();
2369
+ }
2370
+ function onTriggerClick() {
2371
+ if (open.value) close();
2372
+ else openList();
2373
+ }
2374
+ function onKeydown(event) {
2375
+ if (props.disabled) return;
2376
+ const { key } = event;
2377
+ if (!open.value) {
2378
+ if (key === "ArrowDown" || key === "Enter" || key === " ") {
2379
+ event.preventDefault();
2380
+ openList();
2381
+ } else if (key === "ArrowUp") {
2382
+ event.preventDefault();
2383
+ openList();
2384
+ } else if (key === "Home") {
2385
+ event.preventDefault();
2386
+ openList("first");
2387
+ } else if (key === "End") {
2388
+ event.preventDefault();
2389
+ openList("last");
2390
+ } else if (key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {
2391
+ const index = typeahead.search(key, visibleOptions.value, (option) => option.label, activeIndex.value);
2392
+ if (index >= 0) {
2393
+ openList();
2394
+ setActive(index);
2395
+ }
2396
+ }
2397
+ return;
2398
+ }
2399
+ switch (key) {
2400
+ case "ArrowDown":
2401
+ event.preventDefault();
2402
+ if (event.altKey) return;
2403
+ setActive(step(activeIndex.value, 1));
2404
+ break;
2405
+ case "ArrowUp":
2406
+ event.preventDefault();
2407
+ if (event.altKey) return commit(activeIndex.value);
2408
+ setActive(step(activeIndex.value, -1));
2409
+ break;
2410
+ case "Home":
2411
+ event.preventDefault();
2412
+ setActive(first());
2413
+ break;
2414
+ case "End":
2415
+ event.preventDefault();
2416
+ setActive(last());
2417
+ break;
2418
+ case "PageDown":
2419
+ event.preventDefault();
2420
+ setActive(Math.min(last(), step(Math.min(activeIndex.value + 9, visibleOptions.value.length - 1), 1)));
2421
+ break;
2422
+ case "PageUp":
2423
+ event.preventDefault();
2424
+ setActive(Math.max(first(), step(Math.max(activeIndex.value - 9, 0), -1)));
2425
+ break;
2426
+ case "Enter":
2427
+ event.preventDefault();
2428
+ commit(activeIndex.value);
2429
+ break;
2430
+ case "Tab":
2431
+ commit(activeIndex.value);
2432
+ close();
2433
+ break;
2434
+ case " ":
2435
+ event.preventDefault();
2436
+ if (typeahead.isSearching()) typeahead.search(key, visibleOptions.value, (option) => option.label, activeIndex.value);
2437
+ else commit(activeIndex.value);
2438
+ break;
2439
+ default: if (key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {
2440
+ const index = typeahead.search(key, visibleOptions.value, (option) => option.label, activeIndex.value);
2441
+ if (index >= 0) setActive(index);
2442
+ }
2443
+ }
2444
+ }
2445
+ function onOptionPointerMove(index) {
2446
+ pointerActive.value = true;
2447
+ if (activeIndex.value !== index && isEnabled(index)) setActive(index, false);
2448
+ }
2449
+ watch(() => props.disabled, (value) => value && close());
2450
+ __expose({
2451
+ focus: () => trigger.value?.focus(),
2452
+ open: () => openList(),
2453
+ close
2454
+ });
2455
+ return (_ctx, _cache) => {
2456
+ return openBlock(), createBlock(JFieldShell_default, {
2457
+ id: unref(field).id.value,
2458
+ class: normalizeClass(_ctx.$attrs.class),
2459
+ style: normalizeStyle(_ctx.$attrs.style),
2460
+ "label-id": unref(field).labelId.value,
2461
+ "description-id": unref(field).descriptionId.value,
2462
+ "error-id": unref(field).errorId.value,
2463
+ label: __props.label,
2464
+ description: __props.description,
2465
+ "error-message": unref(field).errorMessage.value,
2466
+ "show-description": unref(field).showDescription.value,
2467
+ required: __props.required,
2468
+ disabled: __props.disabled
2469
+ }, createSlots({
2470
+ default: withCtx(() => [
2471
+ createElementVNode("button", mergeProps({
2472
+ id: unref(field).id.value,
2473
+ ref_key: "trigger",
2474
+ ref: trigger
2475
+ }, {
2476
+ ..._ctx.$attrs,
2477
+ class: void 0,
2478
+ style: void 0
2479
+ }, {
2480
+ type: "button",
2481
+ role: "combobox",
2482
+ class: ["j-control j-select__trigger", [`j-control--${__props.size}`, {
2483
+ "is-open": open.value,
2484
+ "is-invalid": unref(field).invalid.value,
2485
+ "is-disabled": __props.disabled
2486
+ }]],
2487
+ "aria-haspopup": "listbox",
2488
+ "aria-expanded": open.value,
2489
+ "aria-controls": open.value ? listboxId.value : void 0,
2490
+ "aria-activedescendant": open.value && activeIndex.value >= 0 ? optionId(activeIndex.value) : void 0,
2491
+ "aria-labelledby": __props.label || _ctx.$slots.label ? `${unref(field).labelId.value} ${valueId.value}` : void 0,
2492
+ "aria-describedby": unref(field).describedBy.value,
2493
+ "aria-invalid": unref(field).invalid.value || void 0,
2494
+ "aria-required": __props.required || void 0,
2495
+ disabled: __props.disabled,
2496
+ onClick: onTriggerClick,
2497
+ onKeydown
2498
+ }), [createElementVNode("span", {
2499
+ id: valueId.value,
2500
+ class: normalizeClass(["j-select__value", { "is-placeholder": !selected.value }])
2501
+ }, [selected.value ? renderSlot(_ctx.$slots, "value", { option: selected.value }, () => [createTextVNode(toDisplayString(selected.value.label), 1)], void 0, 0) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(__props.placeholder), 1)], 64))], 10, _hoisted_2$3), createVNode(unref(IconChevronUpDown), { class: "j-select__chevron" })], 16, _hoisted_1$5),
2502
+ __props.name ? (openBlock(), createElementBlock("input", {
2503
+ key: 0,
2504
+ type: "hidden",
2505
+ name: __props.name,
2506
+ value: model.value ?? ""
2507
+ }, null, 8, _hoisted_3$3)) : createCommentVNode("", true),
2508
+ createVNode(JPortal_default, null, {
2509
+ default: withCtx(() => [createVNode(Transition, { name: "j-pop" }, {
2510
+ default: withCtx(() => [open.value ? (openBlock(), createElementBlock("ul", {
2511
+ key: 0,
2512
+ id: listboxId.value,
2513
+ ref_key: "listbox",
2514
+ ref: listbox,
2515
+ role: "listbox",
2516
+ class: normalizeClass(["j-select__listbox j-popover-surface", { "is-pointer": pointerActive.value }]),
2517
+ style: normalizeStyle(unref(styles)),
2518
+ "aria-labelledby": __props.label ? unref(field).labelId.value : void 0,
2519
+ tabindex: "-1",
2520
+ onPointerdown: _cache[0] || (_cache[0] = withModifiers(() => {}, ["prevent"]))
2521
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(visibleOptions.value, (option, index) => {
2522
+ return openBlock(), createElementBlock("li", {
2523
+ id: optionId(index),
2524
+ key: String(option.value),
2525
+ role: "option",
2526
+ class: normalizeClass(["j-select__option", {
2527
+ "is-active": index === activeIndex.value,
2528
+ "is-selected": option.value === model.value
2529
+ }]),
2530
+ "aria-selected": option.value === model.value,
2531
+ "aria-disabled": option.disabled || void 0,
2532
+ onPointermove: ($event) => onOptionPointerMove(index),
2533
+ onClick: ($event) => commit(index)
2534
+ }, [createElementVNode("span", _hoisted_6$2, [renderSlot(_ctx.$slots, "option", {
2535
+ option,
2536
+ selected: option.value === model.value,
2537
+ active: index === activeIndex.value
2538
+ }, () => [createElementVNode("span", _hoisted_7, toDisplayString(option.label), 1), option.description ? (openBlock(), createElementBlock("span", _hoisted_8, toDisplayString(option.description), 1)) : createCommentVNode("", true)])]), createVNode(unref(IconCheck), { class: "j-select__check" })], 42, _hoisted_5$2);
2539
+ }), 128)), visibleOptions.value.length === 0 ? (openBlock(), createElementBlock("li", _hoisted_9, " No options ")) : createCommentVNode("", true)], 46, _hoisted_4$2)) : createCommentVNode("", true)]),
2540
+ _: 3
2541
+ })]),
2542
+ _: 3
2543
+ })
2544
+ ]),
2545
+ _: 2
2546
+ }, [_ctx.$slots.label ? {
2547
+ name: "label",
2548
+ fn: withCtx(() => [renderSlot(_ctx.$slots, "label")]),
2549
+ key: "0"
2550
+ } : void 0]), 1032, [
2551
+ "id",
2552
+ "class",
2553
+ "style",
2554
+ "label-id",
2555
+ "description-id",
2556
+ "error-id",
2557
+ "label",
2558
+ "description",
2559
+ "error-message",
2560
+ "show-description",
2561
+ "required",
2562
+ "disabled"
2563
+ ]);
2564
+ };
2565
+ }
2566
+ });
2567
+ //#endregion
2568
+ //#region src/components/switch/JSwitch.vue?vue&type=script&setup=true&lang.ts
2569
+ var _hoisted_1$4 = ["data-state"];
2570
+ var _hoisted_2$2 = [
2571
+ "id",
2572
+ "aria-checked",
2573
+ "aria-labelledby",
2574
+ "aria-describedby",
2575
+ "disabled"
2576
+ ];
2577
+ var _hoisted_3$2 = ["name", "value"];
2578
+ var _hoisted_4$1 = {
2579
+ key: 1,
2580
+ class: "j-switch__text"
2581
+ };
2582
+ var _hoisted_5$1 = ["id", "for"];
2583
+ var _hoisted_6$1 = ["id"];
2584
+ //#endregion
2585
+ //#region src/components/switch/JSwitch.vue
2586
+ var JSwitch_default = /* @__PURE__ */ defineComponent({
2587
+ inheritAttrs: false,
2588
+ __name: "JSwitch",
2589
+ props: /*@__PURE__*/ mergeModels({
2590
+ label: {},
2591
+ description: {},
2592
+ disabled: { type: Boolean },
2593
+ size: { default: "md" },
2594
+ name: {},
2595
+ value: { default: "on" },
2596
+ id: {}
2597
+ }, {
2598
+ "modelValue": {
2599
+ type: Boolean,
2600
+ default: false
2601
+ },
2602
+ "modelModifiers": {}
2603
+ }),
2604
+ emits: ["update:modelValue"],
2605
+ setup(__props, { expose: __expose }) {
2606
+ const props = __props;
2607
+ const model = useModel(__props, "modelValue");
2608
+ const generated = useId();
2609
+ const id = computed(() => props.id ?? `j-${generated}`);
2610
+ const labelId = computed(() => `${id.value}-label`);
2611
+ const descriptionId = computed(() => `${id.value}-description`);
2612
+ const button = ref(null);
2613
+ function toggle() {
2614
+ if (props.disabled) return;
2615
+ model.value = !model.value;
2616
+ }
2617
+ __expose({ focus: () => button.value?.focus() });
2618
+ return (_ctx, _cache) => {
2619
+ return openBlock(), createElementBlock("div", {
2620
+ class: normalizeClass(["j-switch", [
2621
+ _ctx.$attrs.class,
2622
+ `j-switch--${__props.size}`,
2623
+ { "is-disabled": __props.disabled }
2624
+ ]]),
2625
+ style: normalizeStyle(_ctx.$attrs.style),
2626
+ "data-state": model.value ? "on" : "off"
2627
+ }, [
2628
+ createElementVNode("button", mergeProps({
2629
+ id: id.value,
2630
+ ref_key: "button",
2631
+ ref: button
2632
+ }, {
2633
+ ..._ctx.$attrs,
2634
+ class: void 0,
2635
+ style: void 0
2636
+ }, {
2637
+ type: "button",
2638
+ role: "switch",
2639
+ class: "j-switch__track",
2640
+ "aria-checked": model.value,
2641
+ "aria-labelledby": __props.label || _ctx.$slots.default ? labelId.value : void 0,
2642
+ "aria-describedby": __props.description || _ctx.$slots.description ? descriptionId.value : void 0,
2643
+ disabled: __props.disabled,
2644
+ onClick: toggle
2645
+ }), [..._cache[0] || (_cache[0] = [createElementVNode("span", { class: "j-switch__thumb" }, null, -1)])], 16, _hoisted_2$2),
2646
+ __props.name && model.value ? (openBlock(), createElementBlock("input", {
2647
+ key: 0,
2648
+ type: "hidden",
2649
+ name: __props.name,
2650
+ value: __props.value
2651
+ }, null, 8, _hoisted_3$2)) : createCommentVNode("", true),
2652
+ __props.label || _ctx.$slots.default || __props.description || _ctx.$slots.description ? (openBlock(), createElementBlock("span", _hoisted_4$1, [__props.label || _ctx.$slots.default ? (openBlock(), createElementBlock("label", {
2653
+ key: 0,
2654
+ id: labelId.value,
2655
+ for: id.value,
2656
+ class: "j-switch__label"
2657
+ }, [renderSlot(_ctx.$slots, "default", {}, () => [createTextVNode(toDisplayString(__props.label), 1)])], 8, _hoisted_5$1)) : createCommentVNode("", true), __props.description || _ctx.$slots.description ? (openBlock(), createElementBlock("span", {
2658
+ key: 1,
2659
+ id: descriptionId.value,
2660
+ class: "j-switch__description"
2661
+ }, [renderSlot(_ctx.$slots, "description", {}, () => [createTextVNode(toDisplayString(__props.description), 1)])], 8, _hoisted_6$1)) : createCommentVNode("", true)])) : createCommentVNode("", true)
2662
+ ], 14, _hoisted_1$4);
2663
+ };
2664
+ }
2665
+ });
2666
+ //#endregion
2667
+ //#region src/components/tabs/JTabs.vue?vue&type=script&setup=true&lang.ts
2668
+ var _hoisted_1$3 = ["aria-label"];
2669
+ var _hoisted_2$1 = [
2670
+ "id",
2671
+ "aria-selected",
2672
+ "aria-controls",
2673
+ "tabindex",
2674
+ "disabled",
2675
+ "onClick"
2676
+ ];
2677
+ var _hoisted_3$1 = [
2678
+ "id",
2679
+ "aria-labelledby",
2680
+ "hidden"
2681
+ ];
2682
+ //#endregion
2683
+ //#region src/components/tabs/JTabs.vue
2684
+ var JTabs_default = /* @__PURE__ */ defineComponent({
2685
+ __name: "JTabs",
2686
+ props: /*@__PURE__*/ mergeModels({
2687
+ items: {},
2688
+ variant: { default: "line" },
2689
+ size: { default: "md" },
2690
+ label: {},
2691
+ fill: { type: Boolean }
2692
+ }, {
2693
+ "modelValue": {},
2694
+ "modelModifiers": {}
2695
+ }),
2696
+ emits: ["update:modelValue"],
2697
+ setup(__props) {
2698
+ const props = __props;
2699
+ const model = useModel(__props, "modelValue");
2700
+ const id = `j-${useId()}`;
2701
+ const tabId = (value) => `${id}-tab-${value}`;
2702
+ const panelId = (value) => `${id}-panel-${value}`;
2703
+ const selected = computed(() => model.value ?? props.items.find((item) => !item.disabled)?.value);
2704
+ const list = ref(null);
2705
+ const indicator = ref({
2706
+ x: 0,
2707
+ width: 0,
2708
+ ready: false
2709
+ });
2710
+ const animated = ref(false);
2711
+ function measure() {
2712
+ const el = list.value?.querySelector("[role=\"tab\"][aria-selected=\"true\"]");
2713
+ indicator.value = el ? {
2714
+ x: el.offsetLeft,
2715
+ width: el.offsetWidth,
2716
+ ready: true
2717
+ } : {
2718
+ ...indicator.value,
2719
+ ready: false
2720
+ };
2721
+ }
2722
+ function measureWithoutMotion() {
2723
+ animated.value = false;
2724
+ measure();
2725
+ requestAnimationFrame(() => requestAnimationFrame(() => animated.value = true));
2726
+ }
2727
+ let observer;
2728
+ onMounted(() => {
2729
+ measureWithoutMotion();
2730
+ observer = new ResizeObserver(measureWithoutMotion);
2731
+ if (list.value) observer.observe(list.value);
2732
+ document.fonts?.ready.then(measureWithoutMotion);
2733
+ });
2734
+ onBeforeUnmount(() => observer?.disconnect());
2735
+ watch(selected, () => nextTick(measure), { flush: "post" });
2736
+ watch(() => props.items, () => nextTick(measure), {
2737
+ deep: true,
2738
+ flush: "post"
2739
+ });
2740
+ function select(item) {
2741
+ if (item.disabled) return;
2742
+ model.value = item.value;
2743
+ }
2744
+ function onKeydown(event) {
2745
+ const enabled = props.items.filter((item) => !item.disabled);
2746
+ if (!enabled.length) return;
2747
+ const current = enabled.findIndex((item) => item.value === selected.value);
2748
+ let next;
2749
+ if (event.key === "ArrowRight") next = (current + 1) % enabled.length;
2750
+ else if (event.key === "ArrowLeft") next = (current - 1 + enabled.length) % enabled.length;
2751
+ else if (event.key === "Home") next = 0;
2752
+ else if (event.key === "End") next = enabled.length - 1;
2753
+ if (next === void 0) return;
2754
+ event.preventDefault();
2755
+ const item = enabled[next];
2756
+ select(item);
2757
+ nextTick(() => focusElement(document.getElementById(tabId(item.value))));
2758
+ }
2759
+ const indicatorStyle = computed(() => ({
2760
+ transform: `translateX(${indicator.value.x}px)`,
2761
+ width: `${indicator.value.width}px`
2762
+ }));
2763
+ return (_ctx, _cache) => {
2764
+ return openBlock(), createElementBlock("div", { class: normalizeClass(["j-tabs", [
2765
+ `j-tabs--${__props.variant}`,
2766
+ `j-tabs--${__props.size}`,
2767
+ { "j-tabs--fill": __props.fill }
2768
+ ]]) }, [createElementVNode("div", {
2769
+ ref_key: "list",
2770
+ ref: list,
2771
+ role: "tablist",
2772
+ "aria-orientation": "horizontal",
2773
+ "aria-label": __props.label,
2774
+ class: "j-tabs__list",
2775
+ onKeydown
2776
+ }, [createElementVNode("span", {
2777
+ class: normalizeClass(["j-tabs__indicator", {
2778
+ "is-ready": indicator.value.ready,
2779
+ "is-animated": animated.value
2780
+ }]),
2781
+ style: normalizeStyle(indicatorStyle.value),
2782
+ "aria-hidden": "true"
2783
+ }, null, 6), (openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
2784
+ return openBlock(), createElementBlock("button", {
2785
+ id: tabId(item.value),
2786
+ key: item.value,
2787
+ type: "button",
2788
+ role: "tab",
2789
+ class: "j-tabs__tab",
2790
+ "aria-selected": item.value === selected.value,
2791
+ "aria-controls": panelId(item.value),
2792
+ tabindex: item.value === selected.value ? 0 : -1,
2793
+ disabled: item.disabled,
2794
+ onClick: ($event) => select(item)
2795
+ }, [renderSlot(_ctx.$slots, "tab", {
2796
+ item,
2797
+ selected: item.value === selected.value
2798
+ }, () => [createTextVNode(toDisplayString(item.label), 1)])], 8, _hoisted_2$1);
2799
+ }), 128))], 40, _hoisted_1$3), (openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
2800
+ return openBlock(), createElementBlock(Fragment, { key: item.value }, [_ctx.$slots[item.value] ? (openBlock(), createElementBlock("div", {
2801
+ key: 0,
2802
+ id: panelId(item.value),
2803
+ role: "tabpanel",
2804
+ class: "j-tabs__panel",
2805
+ "aria-labelledby": tabId(item.value),
2806
+ hidden: item.value !== selected.value,
2807
+ tabindex: "0"
2808
+ }, [renderSlot(_ctx.$slots, item.value, {
2809
+ item,
2810
+ selected: item.value === selected.value
2811
+ })], 8, _hoisted_3$1)) : createCommentVNode("", true)], 64);
2812
+ }), 128))], 2);
2813
+ };
2814
+ }
2815
+ });
2816
+ //#endregion
2817
+ //#region src/components/textarea/JTextarea.vue?vue&type=script&setup=true&lang.ts
2818
+ var _hoisted_1$2 = [
2819
+ "id",
2820
+ "rows",
2821
+ "name",
2822
+ "placeholder",
2823
+ "disabled",
2824
+ "readonly",
2825
+ "required",
2826
+ "aria-invalid",
2827
+ "aria-describedby"
2828
+ ];
2829
+ //#endregion
2830
+ //#region src/components/textarea/JTextarea.vue
2831
+ var JTextarea_default = /* @__PURE__ */ defineComponent({
2832
+ inheritAttrs: false,
2833
+ __name: "JTextarea",
2834
+ props: /*@__PURE__*/ mergeModels({
2835
+ placeholder: {},
2836
+ rows: { default: 3 },
2837
+ autoresize: { type: Boolean },
2838
+ maxRows: { default: 12 },
2839
+ readonly: { type: Boolean },
2840
+ name: {},
2841
+ label: {},
2842
+ description: {},
2843
+ error: {
2844
+ type: [String, Boolean],
2845
+ default: void 0
2846
+ },
2847
+ required: { type: Boolean },
2848
+ disabled: { type: Boolean },
2849
+ id: {}
2850
+ }, {
2851
+ "modelValue": { default: "" },
2852
+ "modelModifiers": {}
2853
+ }),
2854
+ emits: ["update:modelValue"],
2855
+ setup(__props, { expose: __expose }) {
2856
+ const props = __props;
2857
+ const model = useModel(__props, "modelValue");
2858
+ const field = useField(props);
2859
+ const textarea = ref(null);
2860
+ function resize() {
2861
+ const el = textarea.value;
2862
+ if (!props.autoresize || !el) return;
2863
+ const style = getComputedStyle(el);
2864
+ const line = Number.parseFloat(style.lineHeight) || 20;
2865
+ const chrome = Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom);
2866
+ el.style.height = "auto";
2867
+ const max = line * props.maxRows + chrome;
2868
+ const next = Math.min(el.scrollHeight, max);
2869
+ el.style.height = `${next}px`;
2870
+ el.style.overflowY = el.scrollHeight > max ? "auto" : "hidden";
2871
+ }
2872
+ onMounted(resize);
2873
+ watch(model, () => nextTick(resize));
2874
+ watch(() => props.autoresize, (value) => {
2875
+ if (value) return nextTick(resize);
2876
+ if (textarea.value) textarea.value.style.height = "";
2877
+ });
2878
+ __expose({
2879
+ textarea,
2880
+ focus: () => textarea.value?.focus()
2881
+ });
2882
+ return (_ctx, _cache) => {
2883
+ return openBlock(), createBlock(JFieldShell_default, {
2884
+ id: unref(field).id.value,
2885
+ class: normalizeClass(_ctx.$attrs.class),
2886
+ style: normalizeStyle(_ctx.$attrs.style),
2887
+ "label-id": unref(field).labelId.value,
2888
+ "description-id": unref(field).descriptionId.value,
2889
+ "error-id": unref(field).errorId.value,
2890
+ label: __props.label,
2891
+ description: __props.description,
2892
+ "error-message": unref(field).errorMessage.value,
2893
+ "show-description": unref(field).showDescription.value,
2894
+ required: __props.required,
2895
+ disabled: __props.disabled
2896
+ }, createSlots({
2897
+ default: withCtx(() => [createElementVNode("div", { class: normalizeClass(["j-control j-textarea", {
2898
+ "is-invalid": unref(field).invalid.value,
2899
+ "is-disabled": __props.disabled,
2900
+ "is-readonly": __props.readonly
2901
+ }]) }, [withDirectives(createElementVNode("textarea", mergeProps({
2902
+ id: unref(field).id.value,
2903
+ ref_key: "textarea",
2904
+ ref: textarea,
2905
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => model.value = $event)
2906
+ }, {
2907
+ ..._ctx.$attrs,
2908
+ class: void 0,
2909
+ style: void 0
2910
+ }, {
2911
+ class: ["j-textarea__native", { "is-autoresize": __props.autoresize }],
2912
+ rows: __props.rows,
2913
+ name: __props.name,
2914
+ placeholder: __props.placeholder,
2915
+ disabled: __props.disabled,
2916
+ readonly: __props.readonly,
2917
+ required: __props.required,
2918
+ "aria-invalid": unref(field).invalid.value || void 0,
2919
+ "aria-describedby": unref(field).describedBy.value
2920
+ }), null, 16, _hoisted_1$2), [[vModelText, model.value]])], 2)]),
2921
+ _: 2
2922
+ }, [_ctx.$slots.label ? {
2923
+ name: "label",
2924
+ fn: withCtx(() => [renderSlot(_ctx.$slots, "label")]),
2925
+ key: "0"
2926
+ } : void 0]), 1032, [
2927
+ "id",
2928
+ "class",
2929
+ "style",
2930
+ "label-id",
2931
+ "description-id",
2932
+ "error-id",
2933
+ "label",
2934
+ "description",
2935
+ "error-message",
2936
+ "show-description",
2937
+ "required",
2938
+ "disabled"
2939
+ ]);
2940
+ };
2941
+ }
2942
+ });
2943
+ //#endregion
2944
+ //#region src/composables/useToast.ts
2945
+ var DEFAULT_TOAST_DURATION = 5e3;
2946
+ var state = reactive({ toasts: [] });
2947
+ var counter = 0;
2948
+ function resolveDuration(type, duration) {
2949
+ if (duration !== void 0) return duration;
2950
+ if (type === "loading") return Number.POSITIVE_INFINITY;
2951
+ if (type === "error") return 7e3;
2952
+ return DEFAULT_TOAST_DURATION;
2953
+ }
2954
+ function create(title, options = {}) {
2955
+ const type = options.type ?? "default";
2956
+ const existing = options.id ? state.toasts.find((t) => t.id === options.id) : void 0;
2957
+ if (existing) {
2958
+ update(existing.id, {
2959
+ title,
2960
+ ...options
2961
+ });
2962
+ return existing.id;
2963
+ }
2964
+ const id = options.id ?? `toast-${++counter}`;
2965
+ state.toasts.push({
2966
+ id,
2967
+ title,
2968
+ description: options.description,
2969
+ type,
2970
+ duration: resolveDuration(type, options.duration),
2971
+ action: options.action,
2972
+ dismissible: options.dismissible ?? true,
2973
+ onDismiss: options.onDismiss,
2974
+ version: 0
2975
+ });
2976
+ return id;
2977
+ }
2978
+ function update(id, patch) {
2979
+ const item = state.toasts.find((t) => t.id === id);
2980
+ if (!item) return;
2981
+ const type = patch.type ?? item.type;
2982
+ Object.assign(item, {
2983
+ ...patch,
2984
+ type,
2985
+ duration: patch.duration !== void 0 || patch.type ? resolveDuration(type, patch.duration) : item.duration,
2986
+ version: item.version + 1
2987
+ });
2988
+ }
2989
+ function dismiss(id) {
2990
+ if (id === void 0) {
2991
+ state.toasts.splice(0).forEach((t) => t.onDismiss?.());
2992
+ return;
2993
+ }
2994
+ const index = state.toasts.findIndex((t) => t.id === id);
2995
+ if (index === -1) return;
2996
+ const [removed] = state.toasts.splice(index, 1);
2997
+ removed?.onDismiss?.();
2998
+ }
2999
+ function promise(input, messages) {
3000
+ const id = create(messages.loading, {
3001
+ type: "loading",
3002
+ description: messages.description
3003
+ });
3004
+ const run = typeof input === "function" ? input() : input;
3005
+ run.then((value) => {
3006
+ const title = typeof messages.success === "function" ? messages.success(value) : messages.success;
3007
+ update(id, {
3008
+ title,
3009
+ type: "success",
3010
+ description: void 0
3011
+ });
3012
+ }, (error) => {
3013
+ const title = typeof messages.error === "function" ? messages.error(error) : messages.error;
3014
+ update(id, {
3015
+ title,
3016
+ type: "error",
3017
+ description: void 0
3018
+ });
3019
+ });
3020
+ return run;
3021
+ }
3022
+ var typed = (type) => (title, options) => create(title, {
3023
+ ...options,
3024
+ type
3025
+ });
3026
+ /** Show a toast. Works anywhere — inside or outside components. */
3027
+ var toast = Object.assign(create, {
3028
+ success: typed("success"),
3029
+ error: typed("error"),
3030
+ warning: typed("warning"),
3031
+ info: typed("info"),
3032
+ loading: typed("loading"),
3033
+ promise,
3034
+ update,
3035
+ dismiss
3036
+ });
3037
+ function useToast() {
3038
+ return {
3039
+ toast,
3040
+ dismiss,
3041
+ update,
3042
+ toasts: readonly(state).toasts
3043
+ };
3044
+ }
3045
+ /** @internal Mutable access for JToaster and tests. */
3046
+ var toastState = state;
3047
+ //#endregion
3048
+ //#region src/components/toast/JToastItem.vue?vue&type=script&setup=true&lang.ts
3049
+ var _hoisted_1$1 = ["data-swipe-direction", "role"];
3050
+ var _hoisted_2 = {
3051
+ key: 0,
3052
+ class: "j-toast__icon",
3053
+ "aria-hidden": "true"
3054
+ };
3055
+ var _hoisted_3 = { class: "j-toast__body" };
3056
+ var _hoisted_4 = { class: "j-toast__title" };
3057
+ var _hoisted_5 = {
3058
+ key: 0,
3059
+ class: "j-toast__description"
3060
+ };
3061
+ var _hoisted_6 = {
3062
+ key: 1,
3063
+ class: "j-toast__actions"
3064
+ };
3065
+ //#endregion
3066
+ //#region src/components/toast/JToastItem.vue
3067
+ var JToastItem_default = /* @__PURE__ */ defineComponent({
3068
+ __name: "JToastItem",
3069
+ props: {
3070
+ toast: {},
3071
+ paused: { type: Boolean }
3072
+ },
3073
+ emits: ["dismiss"],
3074
+ setup(__props, { emit: __emit }) {
3075
+ /** Internal: one toast. Owns its timer, swipe gesture and keyboard dismissal. */
3076
+ const props = __props;
3077
+ const emit = __emit;
3078
+ const el = ref(null);
3079
+ const timed = computed(() => Number.isFinite(props.toast.duration) && props.toast.duration > 0);
3080
+ let timer;
3081
+ let remaining = 0;
3082
+ let startedAt = 0;
3083
+ function start() {
3084
+ clearTimeout(timer);
3085
+ if (!timed.value || props.paused) return;
3086
+ startedAt = Date.now();
3087
+ timer = setTimeout(() => emit("dismiss", props.toast.id), remaining);
3088
+ }
3089
+ function pause() {
3090
+ if (!timer) return;
3091
+ clearTimeout(timer);
3092
+ timer = void 0;
3093
+ remaining -= Date.now() - startedAt;
3094
+ }
3095
+ function reset() {
3096
+ remaining = props.toast.duration;
3097
+ start();
3098
+ }
3099
+ onMounted(reset);
3100
+ onBeforeUnmount(() => clearTimeout(timer));
3101
+ watch(() => props.toast.version, reset);
3102
+ watch(() => props.paused, (value) => value ? pause() : start());
3103
+ const offset = ref(0);
3104
+ const swiping = ref(false);
3105
+ const leaving = ref(0);
3106
+ let origin = null;
3107
+ function onPointerDown(event) {
3108
+ if (event.button !== 0 || event.target.closest("button, a")) return;
3109
+ origin = {
3110
+ x: event.clientX,
3111
+ y: event.clientY,
3112
+ id: event.pointerId
3113
+ };
3114
+ }
3115
+ function onPointerMove(event) {
3116
+ if (!origin || event.pointerId !== origin.id) return;
3117
+ const dx = event.clientX - origin.x;
3118
+ if (!swiping.value) {
3119
+ if (Math.abs(dx) < 6 || Math.abs(dx) < Math.abs(event.clientY - origin.y)) return;
3120
+ swiping.value = true;
3121
+ el.value?.setPointerCapture(event.pointerId);
3122
+ }
3123
+ offset.value = dx;
3124
+ }
3125
+ function onPointerUp() {
3126
+ if (!origin) return;
3127
+ origin = null;
3128
+ if (!swiping.value) return;
3129
+ swiping.value = false;
3130
+ if (Math.abs(offset.value) > 64 && props.toast.dismissible) {
3131
+ leaving.value = offset.value > 0 ? 1 : -1;
3132
+ emit("dismiss", props.toast.id);
3133
+ } else offset.value = 0;
3134
+ }
3135
+ const style = computed(() => {
3136
+ if (!offset.value) return void 0;
3137
+ return {
3138
+ "--j-toast-swipe": `${offset.value}px`,
3139
+ "--j-toast-swipe-opacity": String(Math.max(0, 1 - Math.abs(offset.value) / 200))
3140
+ };
3141
+ });
3142
+ function onKeydown(event) {
3143
+ if (event.key === "Escape" && props.toast.dismissible) {
3144
+ event.preventDefault();
3145
+ event.stopPropagation();
3146
+ const next = el.value?.nextElementSibling ?? el.value?.previousElementSibling;
3147
+ emit("dismiss", props.toast.id);
3148
+ next?.focus();
3149
+ }
3150
+ }
3151
+ function runAction() {
3152
+ props.toast.action?.onClick();
3153
+ emit("dismiss", props.toast.id);
3154
+ }
3155
+ const icons = {
3156
+ success: IconCircleCheck,
3157
+ error: IconCircleX,
3158
+ warning: IconAlert,
3159
+ info: IconInfo
3160
+ };
3161
+ return (_ctx, _cache) => {
3162
+ return openBlock(), createElementBlock("li", {
3163
+ ref_key: "el",
3164
+ ref: el,
3165
+ class: normalizeClass(["j-toast", [`j-toast--${__props.toast.type}`, {
3166
+ "is-swiping": swiping.value,
3167
+ "is-swiped": leaving.value !== 0
3168
+ }]]),
3169
+ style: normalizeStyle(style.value),
3170
+ "data-swipe-direction": leaving.value,
3171
+ role: __props.toast.type === "error" ? "alert" : void 0,
3172
+ tabindex: "0",
3173
+ onPointerdown: onPointerDown,
3174
+ onPointermove: onPointerMove,
3175
+ onPointerup: onPointerUp,
3176
+ onPointercancel: onPointerUp,
3177
+ onKeydown
3178
+ }, [
3179
+ __props.toast.type !== "default" ? (openBlock(), createElementBlock("span", _hoisted_2, [createVNode(Transition, {
3180
+ name: "j-toast-icon",
3181
+ mode: "out-in"
3182
+ }, {
3183
+ default: withCtx(() => [__props.toast.type === "loading" ? (openBlock(), createBlock(unref(Spinner), {
3184
+ key: 0,
3185
+ size: 16
3186
+ })) : (openBlock(), createBlock(resolveDynamicComponent(icons[__props.toast.type]), { key: __props.toast.type }))]),
3187
+ _: 1
3188
+ })])) : createCommentVNode("", true),
3189
+ createElementVNode("div", _hoisted_3, [
3190
+ createElementVNode("p", _hoisted_4, toDisplayString(__props.toast.title), 1),
3191
+ __props.toast.description ? (openBlock(), createElementBlock("p", _hoisted_5, toDisplayString(__props.toast.description), 1)) : createCommentVNode("", true),
3192
+ __props.toast.action ? (openBlock(), createElementBlock("div", _hoisted_6, [createElementVNode("button", {
3193
+ type: "button",
3194
+ class: "j-toast__action j-focusable",
3195
+ onClick: runAction
3196
+ }, toDisplayString(__props.toast.action.label), 1)])) : createCommentVNode("", true)
3197
+ ]),
3198
+ __props.toast.dismissible ? (openBlock(), createElementBlock("button", {
3199
+ key: 1,
3200
+ type: "button",
3201
+ class: "j-toast__close j-focusable",
3202
+ "aria-label": "Dismiss notification",
3203
+ onClick: _cache[0] || (_cache[0] = ($event) => emit("dismiss", __props.toast.id))
3204
+ }, [createVNode(unref(IconX))])) : createCommentVNode("", true),
3205
+ timed.value ? (openBlock(), createElementBlock("span", {
3206
+ key: __props.toast.version,
3207
+ class: normalizeClass(["j-toast__progress", { "is-paused": __props.paused }]),
3208
+ style: normalizeStyle({ animationDuration: `${__props.toast.duration}ms` }),
3209
+ "aria-hidden": "true"
3210
+ }, null, 6)) : createCommentVNode("", true)
3211
+ ], 46, _hoisted_1$1);
3212
+ };
3213
+ }
3214
+ });
3215
+ //#endregion
3216
+ //#region src/components/toast/JToaster.vue?vue&type=script&setup=true&lang.ts
3217
+ var _hoisted_1 = ["aria-label"];
3218
+ //#endregion
3219
+ //#region src/components/toast/JToaster.vue
3220
+ var JToaster_default = /* @__PURE__ */ defineComponent({
3221
+ __name: "JToaster",
3222
+ props: {
3223
+ position: { default: "bottom-right" },
3224
+ max: { default: 4 },
3225
+ hotkey: { default: "alt+t" },
3226
+ label: { default: "Notifications" }
3227
+ },
3228
+ setup(__props) {
3229
+ const props = __props;
3230
+ const list = ref();
3231
+ const hovered = ref(false);
3232
+ const focused = ref(false);
3233
+ const hidden = ref(false);
3234
+ const paused = computed(() => hovered.value || focused.value || hidden.value);
3235
+ const isTop = computed(() => props.position.startsWith("top"));
3236
+ const visible = computed(() => {
3237
+ const items = toastState.toasts.slice(-props.max);
3238
+ return isTop.value ? items.reverse() : items;
3239
+ });
3240
+ function dismiss(id) {
3241
+ const index = toastState.toasts.findIndex((t) => t.id === id);
3242
+ if (index === -1) return;
3243
+ const [removed] = toastState.toasts.splice(index, 1);
3244
+ removed?.onDismiss?.();
3245
+ }
3246
+ const hotkeyLabel = ref("");
3247
+ onMounted(() => hotkeyLabel.value = formatHotkey(props.hotkey).join("+"));
3248
+ useHotkey(() => props.hotkey, () => {
3249
+ const items = unrefElement(list.value)?.querySelectorAll(".j-toast");
3250
+ if (!items?.length) return;
3251
+ focusElement(isTop.value ? items[0] : items[items.length - 1]);
3252
+ });
3253
+ function onVisibility() {
3254
+ hidden.value = document.visibilityState === "hidden";
3255
+ }
3256
+ onMounted(() => document.addEventListener("visibilitychange", onVisibility));
3257
+ onBeforeUnmount(() => document.removeEventListener("visibilitychange", onVisibility));
3258
+ function onFocusOut(event) {
3259
+ if (!event.currentTarget.contains(event.relatedTarget)) focused.value = false;
3260
+ }
3261
+ return (_ctx, _cache) => {
3262
+ return openBlock(), createBlock(JPortal_default, null, {
3263
+ default: withCtx(() => [createElementVNode("section", {
3264
+ class: normalizeClass(["j-toaster", `j-toaster--${__props.position}`]),
3265
+ "aria-label": hotkeyLabel.value ? `${__props.label} (${hotkeyLabel.value})` : __props.label,
3266
+ "data-juxt-layer-ignore": ""
3267
+ }, [createVNode(TransitionGroup, {
3268
+ ref_key: "list",
3269
+ ref: list,
3270
+ tag: "ol",
3271
+ name: "j-toast",
3272
+ class: "j-toaster__list",
3273
+ "aria-live": "polite",
3274
+ "aria-relevant": "additions text",
3275
+ onPointerenter: _cache[0] || (_cache[0] = ($event) => hovered.value = true),
3276
+ onPointerleave: _cache[1] || (_cache[1] = ($event) => hovered.value = false),
3277
+ onFocusin: _cache[2] || (_cache[2] = ($event) => focused.value = true),
3278
+ onFocusout: onFocusOut
3279
+ }, {
3280
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(visible.value, (item) => {
3281
+ return openBlock(), createBlock(JToastItem_default, {
3282
+ key: item.id,
3283
+ toast: item,
3284
+ paused: paused.value,
3285
+ onDismiss: dismiss
3286
+ }, null, 8, ["toast", "paused"]);
3287
+ }), 128))]),
3288
+ _: 1
3289
+ }, 512)], 10, _hoisted_1)]),
3290
+ _: 1
3291
+ });
3292
+ };
3293
+ }
3294
+ });
3295
+ //#endregion
3296
+ //#region src/components/tooltip/JTooltip.vue?vue&type=script&setup=true&lang.ts
3297
+ var warmUntil = 0;
3298
+ var WARM_WINDOW = 400;
3299
+ //#endregion
3300
+ //#region src/components/tooltip/JTooltip.vue
3301
+ var JTooltip_default = /* @__PURE__ */ defineComponent({
3302
+ __name: "JTooltip",
3303
+ props: {
3304
+ content: {},
3305
+ side: { default: "top" },
3306
+ align: { default: "center" },
3307
+ delay: { default: 450 },
3308
+ disabled: { type: Boolean }
3309
+ },
3310
+ setup(__props) {
3311
+ const props = __props;
3312
+ const id = `j-${useId()}-tooltip`;
3313
+ const open = ref(false);
3314
+ const triggerRef = ref();
3315
+ const triggerEl = computed(() => unrefElement(triggerRef.value));
3316
+ const floating = ref(null);
3317
+ const placement = computed(() => props.align === "center" ? props.side : `${props.side}-${props.align}`);
3318
+ const { styles } = useFloating(triggerEl, floating, () => ({
3319
+ placement: placement.value,
3320
+ offset: 6
3321
+ }));
3322
+ useLayer({
3323
+ active: open,
3324
+ elements: () => [triggerEl.value, floating.value],
3325
+ onEscape: () => hide(true)
3326
+ });
3327
+ let openTimer;
3328
+ let closeTimer;
3329
+ let suppressed = false;
3330
+ function show(immediate = false) {
3331
+ if (props.disabled || suppressed) return;
3332
+ clearTimeout(closeTimer);
3333
+ if (open.value) return;
3334
+ clearTimeout(openTimer);
3335
+ if (immediate || Date.now() < warmUntil) open.value = true;
3336
+ else openTimer = setTimeout(() => open.value = true, props.delay);
3337
+ }
3338
+ function hide(immediate = false) {
3339
+ clearTimeout(openTimer);
3340
+ clearTimeout(closeTimer);
3341
+ const close = () => {
3342
+ if (open.value) warmUntil = Date.now() + WARM_WINDOW;
3343
+ open.value = false;
3344
+ };
3345
+ if (immediate) close();
3346
+ else closeTimer = setTimeout(close, 80);
3347
+ }
3348
+ onBeforeUnmount(() => {
3349
+ clearTimeout(openTimer);
3350
+ clearTimeout(closeTimer);
3351
+ });
3352
+ const triggerProps = computed(() => ({
3353
+ "aria-describedby": props.disabled ? void 0 : id,
3354
+ "onPointerenter": (event) => event.pointerType === "mouse" && show(),
3355
+ "onPointerleave": (event) => {
3356
+ suppressed = false;
3357
+ if (event.pointerType === "mouse") hide();
3358
+ },
3359
+ "onPointerdown": () => {
3360
+ suppressed = true;
3361
+ hide(true);
3362
+ },
3363
+ "onFocus": (event) => {
3364
+ if (event.target.matches(":focus-visible")) show(true);
3365
+ },
3366
+ "onBlur": () => hide(true)
3367
+ }));
3368
+ return (_ctx, _cache) => {
3369
+ return openBlock(), createElementBlock(Fragment, null, [
3370
+ createVNode(unref(JSlot), mergeProps({
3371
+ ref_key: "triggerRef",
3372
+ ref: triggerRef
3373
+ }, triggerProps.value), {
3374
+ default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
3375
+ _: 3
3376
+ }, 16),
3377
+ !__props.disabled ? (openBlock(), createElementBlock("span", {
3378
+ key: 0,
3379
+ id,
3380
+ class: "j-sr-only"
3381
+ }, [renderSlot(_ctx.$slots, "content", {}, () => [createTextVNode(toDisplayString(__props.content), 1)])])) : createCommentVNode("", true),
3382
+ createVNode(JPortal_default, null, {
3383
+ default: withCtx(() => [createVNode(Transition, { name: "j-tooltip" }, {
3384
+ default: withCtx(() => [open.value ? (openBlock(), createElementBlock("div", {
3385
+ key: 0,
3386
+ ref_key: "floating",
3387
+ ref: floating,
3388
+ class: "j-tooltip",
3389
+ style: normalizeStyle(unref(styles)),
3390
+ "aria-hidden": "true",
3391
+ "data-juxt-layer-ignore": "",
3392
+ onPointerenter: _cache[0] || (_cache[0] = ($event) => show(true)),
3393
+ onPointerleave: _cache[1] || (_cache[1] = ($event) => hide())
3394
+ }, [renderSlot(_ctx.$slots, "content", {}, () => [createTextVNode(toDisplayString(__props.content), 1)])], 36)) : createCommentVNode("", true)]),
3395
+ _: 3
3396
+ })]),
3397
+ _: 3
3398
+ })
3399
+ ], 64);
3400
+ };
3401
+ }
3402
+ });
3403
+ //#endregion
3404
+ //#region src/components.ts
3405
+ var components_exports = /* @__PURE__ */ __exportAll({
3406
+ JAvatar: () => JAvatar_default,
3407
+ JBadge: () => JBadge_default,
3408
+ JButton: () => JButton_default,
3409
+ JCard: () => JCard_default,
3410
+ JCheckbox: () => JCheckbox_default,
3411
+ JCommandPalette: () => JCommandPalette_default,
3412
+ JDialog: () => JDialog_default,
3413
+ JDropdownMenu: () => JDropdownMenu_default,
3414
+ JDropdownMenuItem: () => JDropdownMenuItem_default,
3415
+ JDropdownMenuLabel: () => JDropdownMenuLabel_default,
3416
+ JDropdownMenuSeparator: () => JDropdownMenuSeparator_default,
3417
+ JDropdownMenuSub: () => JDropdownMenuSub_default,
3418
+ JInput: () => JInput_default,
3419
+ JSelect: () => JSelect_default,
3420
+ JSwitch: () => JSwitch_default,
3421
+ JTabs: () => JTabs_default,
3422
+ JTextarea: () => JTextarea_default,
3423
+ JToaster: () => JToaster_default,
3424
+ JTooltip: () => JTooltip_default
3425
+ });
3426
+ //#endregion
3427
+ //#region src/composables/useRecentCommands.ts
3428
+ /**
3429
+ * Remembers which commands were used most recently, per browser.
3430
+ * Pair `record(item.id)` in your select handler with `resolve(allItems)`
3431
+ * to feed the palette's `recent` prop.
3432
+ */
3433
+ function useRecentCommands(options = {}) {
3434
+ const key = options.key ?? "juxt:recent-commands";
3435
+ const limit = options.limit ?? 5;
3436
+ const ids = ref([]);
3437
+ function read() {
3438
+ try {
3439
+ const value = JSON.parse(localStorage.getItem(key) ?? "[]");
3440
+ return Array.isArray(value) ? value.filter((id) => typeof id === "string") : [];
3441
+ } catch {
3442
+ return [];
3443
+ }
3444
+ }
3445
+ function write() {
3446
+ try {
3447
+ localStorage.setItem(key, JSON.stringify(ids.value));
3448
+ } catch {}
3449
+ }
3450
+ onMounted(() => ids.value = read());
3451
+ function record(id) {
3452
+ ids.value = [id, ...ids.value.filter((existing) => existing !== id)].slice(0, limit);
3453
+ write();
3454
+ }
3455
+ function clear() {
3456
+ ids.value = [];
3457
+ write();
3458
+ }
3459
+ /** Map remembered ids back to items, dropping any that no longer exist. */
3460
+ function resolve(items) {
3461
+ const byId = new Map(items.map((item) => [item.id, item]));
3462
+ return ids.value.map((id) => byId.get(id)).filter((item) => !!item);
3463
+ }
3464
+ return {
3465
+ ids,
3466
+ record,
3467
+ clear,
3468
+ resolve
3469
+ };
3470
+ }
3471
+ //#endregion
3472
+ //#region src/plugin.ts
3473
+ /**
3474
+ * Registers every Juxt component globally.
3475
+ *
3476
+ * app.use(JuxtUI)
3477
+ *
3478
+ * Prefer direct imports (or the Nuxt module) if you want tree-shaking.
3479
+ */
3480
+ var JuxtUI = { install(app) {
3481
+ for (const [name, component] of Object.entries(components_exports)) app.component(name, component);
3482
+ } };
3483
+ //#endregion
3484
+ export { JAvatar_default as JAvatar, JBadge_default as JBadge, JButton_default as JButton, JCard_default as JCard, JCheckbox_default as JCheckbox, JCommandPalette_default as JCommandPalette, JDialog_default as JDialog, JDropdownMenu_default as JDropdownMenu, JDropdownMenuItem_default as JDropdownMenuItem, JDropdownMenuLabel_default as JDropdownMenuLabel, JDropdownMenuSeparator_default as JDropdownMenuSeparator, JDropdownMenuSub_default as JDropdownMenuSub, JInput_default as JInput, JSelect_default as JSelect, JSwitch_default as JSwitch, JTabs_default as JTabs, JTextarea_default as JTextarea, JToaster_default as JToaster, JTooltip_default as JTooltip, JuxtUI, JuxtUI as default, formatHotkey, matchText, matchesHotkey, toast, useFocusTrap, useHotkey, useRecentCommands, useScrollLock, useToast };
3485
+
3486
+ //# sourceMappingURL=index.js.map