@juxtui/ui 0.5.0 → 0.6.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.
- package/README.md +1 -0
- package/dist/JTooltip-CAL9lsPF.js +1115 -0
- package/dist/JTooltip-CAL9lsPF.js.map +1 -0
- package/dist/composables/useLayer.d.ts +1 -1
- package/dist/editor/JEditor.vue.d.ts +69 -0
- package/dist/editor/JEditorButton.vue.d.ts +25 -0
- package/dist/editor/commands.d.ts +18 -0
- package/dist/editor/icons.d.ts +26 -0
- package/dist/editor/index.d.ts +4 -0
- package/dist/editor/slash.d.ts +19 -0
- package/dist/editor.js +1115 -0
- package/dist/editor.js.map +1 -0
- package/dist/index.js +129 -1238
- package/dist/index.js.map +1 -1
- package/dist/nuxt.js +5 -0
- package/dist/nuxt.js.map +1 -1
- package/dist/style.css +796 -228
- package/package.json +15 -2
|
@@ -0,0 +1,1115 @@
|
|
|
1
|
+
import { Comment, Fragment, Teleport, Text, Transition, cloneVNode, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, h, inject, mergeModels, mergeProps, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, provide, ref, renderList, renderSlot, resolveDynamicComponent, shallowRef, toDisplayString, toRef, toValue, unref, useId, useModel, watch, withCtx } from "vue";
|
|
2
|
+
import { autoUpdate, computePosition, flip, offset, shift, size } from "@floating-ui/dom";
|
|
3
|
+
//#region src/utils/dom.ts
|
|
4
|
+
var isClient = typeof window !== "undefined" && typeof document !== "undefined";
|
|
5
|
+
/** Resolve a template ref that may point at an element or a component instance. */
|
|
6
|
+
function unrefElement(target) {
|
|
7
|
+
const value = unref(target);
|
|
8
|
+
if (!value) return null;
|
|
9
|
+
if (value instanceof Element) return value;
|
|
10
|
+
const el = value.$el;
|
|
11
|
+
if (el && el.nodeType !== Node.ELEMENT_NODE) {
|
|
12
|
+
const next = el.nextSibling;
|
|
13
|
+
return next instanceof HTMLElement ? next : null;
|
|
14
|
+
}
|
|
15
|
+
return el ?? null;
|
|
16
|
+
}
|
|
17
|
+
var FOCUSABLE = [
|
|
18
|
+
"a[href]",
|
|
19
|
+
"area[href]",
|
|
20
|
+
"button:not([disabled])",
|
|
21
|
+
"input:not([disabled]):not([type=\"hidden\"])",
|
|
22
|
+
"select:not([disabled])",
|
|
23
|
+
"textarea:not([disabled])",
|
|
24
|
+
"iframe",
|
|
25
|
+
"audio[controls]",
|
|
26
|
+
"video[controls]",
|
|
27
|
+
"[contenteditable]:not([contenteditable=\"false\"])",
|
|
28
|
+
"[tabindex]"
|
|
29
|
+
].join(",");
|
|
30
|
+
function isVisible(el) {
|
|
31
|
+
if (el.hidden) return false;
|
|
32
|
+
if (typeof el.checkVisibility === "function") return el.checkVisibility({ visibilityProperty: true });
|
|
33
|
+
for (let node = el; node; node = node.parentElement) {
|
|
34
|
+
const style = getComputedStyle(node);
|
|
35
|
+
if (style.display === "none" || style.visibility === "hidden") return false;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
/** Elements that can receive keyboard focus, in DOM order. */
|
|
40
|
+
function getFocusableElements(container) {
|
|
41
|
+
return Array.from(container.querySelectorAll(FOCUSABLE)).filter((el) => el.tabIndex >= 0 && !el.closest("[inert]") && isVisible(el));
|
|
42
|
+
}
|
|
43
|
+
function focusElement(el, options) {
|
|
44
|
+
if (!el) return;
|
|
45
|
+
el.focus({
|
|
46
|
+
preventScroll: true,
|
|
47
|
+
...options
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
var isMac = () => isClient && /mac|iphone|ipad|ipod/i.test(navigator.platform || navigator.userAgent);
|
|
51
|
+
function prefersReducedMotion() {
|
|
52
|
+
return isClient && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/icons.ts
|
|
56
|
+
function icon(name, children, viewBox = "0 0 16 16") {
|
|
57
|
+
const component = (props) => h("svg", {
|
|
58
|
+
"xmlns": "http://www.w3.org/2000/svg",
|
|
59
|
+
viewBox,
|
|
60
|
+
"width": 16,
|
|
61
|
+
"height": 16,
|
|
62
|
+
"fill": "none",
|
|
63
|
+
"stroke": "currentColor",
|
|
64
|
+
"stroke-width": 1.5,
|
|
65
|
+
"stroke-linecap": "round",
|
|
66
|
+
"stroke-linejoin": "round",
|
|
67
|
+
"aria-hidden": "true",
|
|
68
|
+
"focusable": "false",
|
|
69
|
+
...props
|
|
70
|
+
}, children());
|
|
71
|
+
component.displayName = name;
|
|
72
|
+
return component;
|
|
73
|
+
}
|
|
74
|
+
var IconCheck = icon("IconCheck", () => [h("path", { d: "M3.5 8.5l3 3 6-7" })]);
|
|
75
|
+
var IconChevronDown = icon("IconChevronDown", () => [h("path", { d: "M4.5 6.25L8 9.75l3.5-3.5" })]);
|
|
76
|
+
var IconChevronUpDown = icon("IconChevronUpDown", () => [h("path", { d: "M5.25 6L8 3.25 10.75 6M5.25 10L8 12.75 10.75 10" })]);
|
|
77
|
+
var IconChevronRight = icon("IconChevronRight", () => [h("path", { d: "M6.25 4.5L9.75 8l-3.5 3.5" })]);
|
|
78
|
+
var IconX = icon("IconX", () => [h("path", { d: "M4.5 4.5l7 7M11.5 4.5l-7 7" })]);
|
|
79
|
+
var IconSearch = icon("IconSearch", () => [h("circle", {
|
|
80
|
+
cx: 7,
|
|
81
|
+
cy: 7,
|
|
82
|
+
r: 4.25
|
|
83
|
+
}), h("path", { d: "M10.25 10.25L13 13" })]);
|
|
84
|
+
var IconCircleCheck = icon("IconCircleCheck", () => [h("circle", {
|
|
85
|
+
cx: 8,
|
|
86
|
+
cy: 8,
|
|
87
|
+
r: 6.25
|
|
88
|
+
}), h("path", { d: "M5.5 8.25l1.75 1.75L10.5 6.5" })]);
|
|
89
|
+
var IconCircleX = icon("IconCircleX", () => [h("circle", {
|
|
90
|
+
cx: 8,
|
|
91
|
+
cy: 8,
|
|
92
|
+
r: 6.25
|
|
93
|
+
}), h("path", { d: "M6.25 6.25l3.5 3.5M9.75 6.25l-3.5 3.5" })]);
|
|
94
|
+
var IconAlert = icon("IconAlert", () => [
|
|
95
|
+
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" }),
|
|
96
|
+
h("path", { d: "M8 6.5v2.5" }),
|
|
97
|
+
h("path", {
|
|
98
|
+
"d": "M8 11.1v.01",
|
|
99
|
+
"stroke-width": 1.75
|
|
100
|
+
})
|
|
101
|
+
]);
|
|
102
|
+
var IconInfo = icon("IconInfo", () => [
|
|
103
|
+
h("circle", {
|
|
104
|
+
cx: 8,
|
|
105
|
+
cy: 8,
|
|
106
|
+
r: 6.25
|
|
107
|
+
}),
|
|
108
|
+
h("path", { d: "M8 7.5v3.25" }),
|
|
109
|
+
h("path", {
|
|
110
|
+
"d": "M8 5.25v.01",
|
|
111
|
+
"stroke-width": 1.75
|
|
112
|
+
})
|
|
113
|
+
]);
|
|
114
|
+
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" })]);
|
|
115
|
+
var IconChevronLeft = icon("IconChevronLeft", () => [h("path", { d: "M9.75 4.5L6.25 8l3.5 3.5" })]);
|
|
116
|
+
var IconMoreHorizontal = icon("IconMoreHorizontal", () => [
|
|
117
|
+
h("circle", {
|
|
118
|
+
cx: 3.5,
|
|
119
|
+
cy: 8,
|
|
120
|
+
r: 1,
|
|
121
|
+
"fill": "currentColor",
|
|
122
|
+
"stroke": "none"
|
|
123
|
+
}),
|
|
124
|
+
h("circle", {
|
|
125
|
+
cx: 8,
|
|
126
|
+
cy: 8,
|
|
127
|
+
r: 1,
|
|
128
|
+
"fill": "currentColor",
|
|
129
|
+
"stroke": "none"
|
|
130
|
+
}),
|
|
131
|
+
h("circle", {
|
|
132
|
+
cx: 12.5,
|
|
133
|
+
cy: 8,
|
|
134
|
+
r: 1,
|
|
135
|
+
"fill": "currentColor",
|
|
136
|
+
"stroke": "none"
|
|
137
|
+
})
|
|
138
|
+
]);
|
|
139
|
+
var IconArrowUpRight = icon("IconArrowUpRight", () => [h("path", { d: "M5 11L11 5M5.5 5h5.5v5.5" })]);
|
|
140
|
+
var IconArrowDownRight = icon("IconArrowDownRight", () => [h("path", { d: "M5 5l6 6M10.5 11H5V5.5" })]);
|
|
141
|
+
/** The loading spinner used across Juxt. A quarter arc over a faint track. */
|
|
142
|
+
var Spinner = (props) => {
|
|
143
|
+
const size = props.size ?? 16;
|
|
144
|
+
return h("svg", {
|
|
145
|
+
"class": "j-spinner",
|
|
146
|
+
"viewBox": "0 0 16 16",
|
|
147
|
+
"width": size,
|
|
148
|
+
"height": size,
|
|
149
|
+
"aria-hidden": "true",
|
|
150
|
+
"focusable": "false"
|
|
151
|
+
}, [h("circle", {
|
|
152
|
+
"class": "j-spinner__track",
|
|
153
|
+
"cx": 8,
|
|
154
|
+
"cy": 8,
|
|
155
|
+
"r": 6,
|
|
156
|
+
"stroke-width": 1.75
|
|
157
|
+
}), h("circle", {
|
|
158
|
+
"cx": 8,
|
|
159
|
+
"cy": 8,
|
|
160
|
+
"r": 6,
|
|
161
|
+
"stroke-width": 1.75,
|
|
162
|
+
"stroke-dasharray": "9.5 28",
|
|
163
|
+
"stroke-dashoffset": 0
|
|
164
|
+
})]);
|
|
165
|
+
};
|
|
166
|
+
Spinner.displayName = "JSpinner";
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/composables/useHotkey.ts
|
|
169
|
+
/**
|
|
170
|
+
* Parse a shortcut such as `mod+k`, `shift+/` or `alt+t`.
|
|
171
|
+
* `mod` is ⌘ on Apple platforms and Ctrl elsewhere.
|
|
172
|
+
*/
|
|
173
|
+
function matchesHotkey(event, hotkey) {
|
|
174
|
+
const parts = hotkey.toLowerCase().split("+").map((part) => part.trim());
|
|
175
|
+
const key = parts.pop();
|
|
176
|
+
if (!key) return false;
|
|
177
|
+
const mac = isMac();
|
|
178
|
+
const want = {
|
|
179
|
+
meta: parts.includes("meta") || mac && parts.includes("mod"),
|
|
180
|
+
ctrl: parts.includes("ctrl") || !mac && parts.includes("mod"),
|
|
181
|
+
alt: parts.includes("alt"),
|
|
182
|
+
shift: parts.includes("shift")
|
|
183
|
+
};
|
|
184
|
+
if (event.metaKey !== want.meta || event.ctrlKey !== want.ctrl || event.altKey !== want.alt || event.shiftKey !== want.shift) return false;
|
|
185
|
+
const code = event.code.toLowerCase();
|
|
186
|
+
return event.key.toLowerCase() === key || code === `key${key}` || code === `digit${key}`;
|
|
187
|
+
}
|
|
188
|
+
/** Human-readable parts of a shortcut, e.g. `mod+k` → `['⌘', 'K']`. */
|
|
189
|
+
function formatHotkey(hotkey) {
|
|
190
|
+
const labels = isMac() ? {
|
|
191
|
+
mod: "⌘",
|
|
192
|
+
meta: "⌘",
|
|
193
|
+
ctrl: "⌃",
|
|
194
|
+
alt: "⌥",
|
|
195
|
+
shift: "⇧",
|
|
196
|
+
enter: "↵",
|
|
197
|
+
escape: "Esc"
|
|
198
|
+
} : {
|
|
199
|
+
mod: "Ctrl",
|
|
200
|
+
meta: "Win",
|
|
201
|
+
ctrl: "Ctrl",
|
|
202
|
+
alt: "Alt",
|
|
203
|
+
shift: "Shift",
|
|
204
|
+
enter: "Enter",
|
|
205
|
+
escape: "Esc"
|
|
206
|
+
};
|
|
207
|
+
return hotkey.split("+").map((part) => {
|
|
208
|
+
const lower = part.trim().toLowerCase();
|
|
209
|
+
return labels[lower] ?? (lower.length === 1 ? lower.toUpperCase() : part.trim());
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
/** Run `handler` whenever the shortcut is pressed anywhere on the page. */
|
|
213
|
+
function useHotkey(hotkey, handler) {
|
|
214
|
+
function onKeydown(event) {
|
|
215
|
+
const value = toValue(hotkey);
|
|
216
|
+
if (!value || event.repeat || event.isComposing || event.defaultPrevented) return;
|
|
217
|
+
if (!matchesHotkey(event, value)) return;
|
|
218
|
+
event.preventDefault();
|
|
219
|
+
handler(event);
|
|
220
|
+
}
|
|
221
|
+
onMounted(() => document.addEventListener("keydown", onKeydown));
|
|
222
|
+
onBeforeUnmount(() => document.removeEventListener("keydown", onKeydown));
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/composables/useLayer.ts
|
|
226
|
+
var layers = [];
|
|
227
|
+
var listening = false;
|
|
228
|
+
function top() {
|
|
229
|
+
return layers[layers.length - 1];
|
|
230
|
+
}
|
|
231
|
+
function onKeydown(event) {
|
|
232
|
+
if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
|
|
233
|
+
const layer = top();
|
|
234
|
+
if (!layer?.options.onEscape) return;
|
|
235
|
+
event.preventDefault();
|
|
236
|
+
event.stopPropagation();
|
|
237
|
+
layer.options.onEscape(event);
|
|
238
|
+
}
|
|
239
|
+
function onPointerDown(event) {
|
|
240
|
+
const layer = top();
|
|
241
|
+
if (!layer?.options.onPointerDownOutside) return;
|
|
242
|
+
const target = event.target;
|
|
243
|
+
if (!target) return;
|
|
244
|
+
if (target.closest?.("[data-juxt-layer-ignore]")) return;
|
|
245
|
+
if (!layer.options.elements().some((el) => el?.contains(target))) layer.options.onPointerDownOutside(event);
|
|
246
|
+
}
|
|
247
|
+
function listen() {
|
|
248
|
+
if (listening || !isClient) return;
|
|
249
|
+
document.addEventListener("keydown", onKeydown);
|
|
250
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
251
|
+
listening = true;
|
|
252
|
+
}
|
|
253
|
+
function unlisten() {
|
|
254
|
+
if (!listening || layers.length > 0) return;
|
|
255
|
+
document.removeEventListener("keydown", onKeydown);
|
|
256
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
257
|
+
listening = false;
|
|
258
|
+
}
|
|
259
|
+
function useLayer(options) {
|
|
260
|
+
const layer = { options };
|
|
261
|
+
const add = () => {
|
|
262
|
+
if (layers.includes(layer)) return;
|
|
263
|
+
layers.push(layer);
|
|
264
|
+
listen();
|
|
265
|
+
};
|
|
266
|
+
const remove = () => {
|
|
267
|
+
const index = layers.indexOf(layer);
|
|
268
|
+
if (index === -1) return;
|
|
269
|
+
layers.splice(index, 1);
|
|
270
|
+
unlisten();
|
|
271
|
+
};
|
|
272
|
+
if (isClient) {
|
|
273
|
+
watch(options.active, (value) => value ? add() : remove(), { immediate: true });
|
|
274
|
+
onBeforeUnmount(remove);
|
|
275
|
+
}
|
|
276
|
+
return { isTopLayer: () => top() === layer };
|
|
277
|
+
}
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region src/components/command-palette/search.ts
|
|
280
|
+
var NO_MATCH = {
|
|
281
|
+
score: 0,
|
|
282
|
+
ranges: []
|
|
283
|
+
};
|
|
284
|
+
function matchText(query, text) {
|
|
285
|
+
const q = query.trim().toLowerCase();
|
|
286
|
+
if (!q) return {
|
|
287
|
+
score: 1,
|
|
288
|
+
ranges: []
|
|
289
|
+
};
|
|
290
|
+
const t = text.toLowerCase();
|
|
291
|
+
if (!t) return NO_MATCH;
|
|
292
|
+
if (t === q) return {
|
|
293
|
+
score: 1e3,
|
|
294
|
+
ranges: [[0, t.length]]
|
|
295
|
+
};
|
|
296
|
+
if (t.startsWith(q)) return {
|
|
297
|
+
score: 900 - Math.min(t.length - q.length, 100),
|
|
298
|
+
ranges: [[0, q.length]]
|
|
299
|
+
};
|
|
300
|
+
const wordStart = findWordStart(t, q);
|
|
301
|
+
if (wordStart > 0) return {
|
|
302
|
+
score: 700 - Math.min(wordStart, 100),
|
|
303
|
+
ranges: [[wordStart, wordStart + q.length]]
|
|
304
|
+
};
|
|
305
|
+
const index = t.indexOf(q);
|
|
306
|
+
if (index > 0) return {
|
|
307
|
+
score: 500 - Math.min(index, 100),
|
|
308
|
+
ranges: [[index, index + q.length]]
|
|
309
|
+
};
|
|
310
|
+
return subsequence(q, t);
|
|
311
|
+
}
|
|
312
|
+
function findWordStart(t, q) {
|
|
313
|
+
let from = 0;
|
|
314
|
+
while (from < t.length) {
|
|
315
|
+
const index = t.indexOf(q, from);
|
|
316
|
+
if (index === -1) return -1;
|
|
317
|
+
if (index > 0 && /[\s\-_/.:]/.test(t[index - 1])) return index;
|
|
318
|
+
from = index + 1;
|
|
319
|
+
}
|
|
320
|
+
return -1;
|
|
321
|
+
}
|
|
322
|
+
/** Letters in order, not necessarily adjacent | "gtst" finds "Go to settings". */
|
|
323
|
+
function subsequence(q, t) {
|
|
324
|
+
const ranges = [];
|
|
325
|
+
let ti = 0;
|
|
326
|
+
let score = 100;
|
|
327
|
+
let previous = -2;
|
|
328
|
+
for (const char of q) {
|
|
329
|
+
if (char === " ") continue;
|
|
330
|
+
const found = t.indexOf(char, ti);
|
|
331
|
+
if (found === -1) return NO_MATCH;
|
|
332
|
+
if (found === previous + 1 && ranges.length) {
|
|
333
|
+
ranges[ranges.length - 1][1] = found + 1;
|
|
334
|
+
score += 12;
|
|
335
|
+
} else {
|
|
336
|
+
ranges.push([found, found + 1]);
|
|
337
|
+
if (found === 0 || /[\s\-_/.:]/.test(t[found - 1])) score += 8;
|
|
338
|
+
}
|
|
339
|
+
score -= Math.min(found - ti, 10);
|
|
340
|
+
previous = found;
|
|
341
|
+
ti = found + 1;
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
score: Math.max(score, 1),
|
|
345
|
+
ranges
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
/** Split text into highlighted and plain runs. */
|
|
349
|
+
function splitByRanges(text, ranges) {
|
|
350
|
+
if (!ranges.length) return [{
|
|
351
|
+
text,
|
|
352
|
+
match: false
|
|
353
|
+
}];
|
|
354
|
+
const parts = [];
|
|
355
|
+
let cursor = 0;
|
|
356
|
+
for (const [start, end] of ranges) {
|
|
357
|
+
if (start > cursor) parts.push({
|
|
358
|
+
text: text.slice(cursor, start),
|
|
359
|
+
match: false
|
|
360
|
+
});
|
|
361
|
+
parts.push({
|
|
362
|
+
text: text.slice(start, end),
|
|
363
|
+
match: true
|
|
364
|
+
});
|
|
365
|
+
cursor = end;
|
|
366
|
+
}
|
|
367
|
+
if (cursor < text.length) parts.push({
|
|
368
|
+
text: text.slice(cursor),
|
|
369
|
+
match: false
|
|
370
|
+
});
|
|
371
|
+
return parts;
|
|
372
|
+
}
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region src/components/primitives/JPortal.vue
|
|
375
|
+
var JPortal_default = /* @__PURE__ */ defineComponent({
|
|
376
|
+
__name: "JPortal",
|
|
377
|
+
props: { to: { default: "body" } },
|
|
378
|
+
setup(__props) {
|
|
379
|
+
/**
|
|
380
|
+
* Internal: teleports its content to `<body>`, but only once mounted.
|
|
381
|
+
*
|
|
382
|
+
* Server-rendered teleports to `body` never make it into the HTML, so a plain
|
|
383
|
+
* `<Teleport>` hydrates against nothing and warns. Rendering nothing until
|
|
384
|
+
* mount keeps server and client output identical | and overlays are closed on
|
|
385
|
+
* first paint anyway.
|
|
386
|
+
*/
|
|
387
|
+
const mounted = ref(false);
|
|
388
|
+
onMounted(() => mounted.value = true);
|
|
389
|
+
return (_ctx, _cache) => {
|
|
390
|
+
return mounted.value ? (openBlock(), createBlock(Teleport, {
|
|
391
|
+
key: 0,
|
|
392
|
+
to: __props.to
|
|
393
|
+
}, [renderSlot(_ctx.$slots, "default")], 8, ["to"])) : createCommentVNode("", true);
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
//#endregion
|
|
398
|
+
//#region src/components/primitives/JSlot.ts
|
|
399
|
+
function firstElement(nodes) {
|
|
400
|
+
if (!nodes) return void 0;
|
|
401
|
+
for (const node of nodes) {
|
|
402
|
+
if (node.type === Comment) continue;
|
|
403
|
+
if (node.type === Fragment && Array.isArray(node.children)) {
|
|
404
|
+
const found = firstElement(node.children);
|
|
405
|
+
if (found) return found;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
if (node.type === Text) {
|
|
409
|
+
if (typeof node.children === "string" && node.children.trim() === "") continue;
|
|
410
|
+
return h("span", null, node.children);
|
|
411
|
+
}
|
|
412
|
+
return node;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Internal: renders its first child element and merges its own attributes
|
|
417
|
+
* (ARIA, listeners, ids) onto it | no wrapper element.
|
|
418
|
+
*/
|
|
419
|
+
var JSlot = defineComponent({
|
|
420
|
+
name: "JSlot",
|
|
421
|
+
inheritAttrs: false,
|
|
422
|
+
setup(_, { slots, attrs }) {
|
|
423
|
+
return () => {
|
|
424
|
+
const child = firstElement(slots.default?.());
|
|
425
|
+
if (!child) return null;
|
|
426
|
+
const { id, ...rest } = attrs;
|
|
427
|
+
return cloneVNode(child, child.props?.id ? rest : {
|
|
428
|
+
id,
|
|
429
|
+
...rest
|
|
430
|
+
}, true);
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/components/dropdown-menu/context.ts
|
|
436
|
+
var MENU_ROOT = Symbol("JDropdownMenu");
|
|
437
|
+
var MENU_LEVEL = Symbol("JDropdownMenuLevel");
|
|
438
|
+
function getMenuItems(content) {
|
|
439
|
+
if (!content) return [];
|
|
440
|
+
return Array.from(content.querySelectorAll("[role^=\"menuitem\"]:not([aria-disabled=\"true\"])"));
|
|
441
|
+
}
|
|
442
|
+
//#endregion
|
|
443
|
+
//#region src/composables/useFloating.ts
|
|
444
|
+
var ORIGIN = {
|
|
445
|
+
top: "bottom",
|
|
446
|
+
bottom: "top",
|
|
447
|
+
left: "right",
|
|
448
|
+
right: "left"
|
|
449
|
+
};
|
|
450
|
+
/**
|
|
451
|
+
* Position a floating element next to a reference element and keep it there
|
|
452
|
+
* through scroll and resize. Coordinates are written to `left`/`top` so
|
|
453
|
+
* `transform` stays free for enter/leave motion.
|
|
454
|
+
*/
|
|
455
|
+
function useFloating(reference, floating, options = {}) {
|
|
456
|
+
const read = () => typeof options === "function" ? options() : options;
|
|
457
|
+
const x = ref(0);
|
|
458
|
+
const y = ref(0);
|
|
459
|
+
const placement = ref(read().placement ?? "bottom-start");
|
|
460
|
+
const availableHeight = ref();
|
|
461
|
+
const referenceWidth = ref();
|
|
462
|
+
const stop = shallowRef();
|
|
463
|
+
async function update() {
|
|
464
|
+
const referenceEl = reference.value;
|
|
465
|
+
const floatingEl = floating.value;
|
|
466
|
+
if (!referenceEl || !floatingEl) return;
|
|
467
|
+
const opts = read();
|
|
468
|
+
const padding = opts.padding ?? 8;
|
|
469
|
+
const result = await computePosition(referenceEl, floatingEl, {
|
|
470
|
+
strategy: "fixed",
|
|
471
|
+
placement: opts.placement ?? "bottom-start",
|
|
472
|
+
middleware: [
|
|
473
|
+
offset(opts.offset ?? 6),
|
|
474
|
+
flip({ padding }),
|
|
475
|
+
shift({ padding }),
|
|
476
|
+
size({
|
|
477
|
+
padding,
|
|
478
|
+
apply({ availableHeight: height, rects }) {
|
|
479
|
+
availableHeight.value = Math.max(120, Math.floor(height));
|
|
480
|
+
referenceWidth.value = rects.reference.width;
|
|
481
|
+
}
|
|
482
|
+
})
|
|
483
|
+
]
|
|
484
|
+
});
|
|
485
|
+
x.value = Math.round(result.x);
|
|
486
|
+
y.value = Math.round(result.y);
|
|
487
|
+
placement.value = result.placement;
|
|
488
|
+
}
|
|
489
|
+
watch([reference, floating], ([referenceEl, floatingEl]) => {
|
|
490
|
+
stop.value?.();
|
|
491
|
+
stop.value = void 0;
|
|
492
|
+
if (referenceEl && floatingEl) stop.value = autoUpdate(referenceEl, floatingEl, update);
|
|
493
|
+
}, { flush: "post" });
|
|
494
|
+
onBeforeUnmount(() => stop.value?.());
|
|
495
|
+
const side = computed(() => placement.value.split("-")[0]);
|
|
496
|
+
return {
|
|
497
|
+
x,
|
|
498
|
+
y,
|
|
499
|
+
placement,
|
|
500
|
+
side,
|
|
501
|
+
styles: computed(() => {
|
|
502
|
+
const align = placement.value.split("-")[1];
|
|
503
|
+
const cross = side.value === "top" || side.value === "bottom" ? align === "start" ? "left" : align === "end" ? "right" : "center" : align === "start" ? "top" : align === "end" ? "bottom" : "center";
|
|
504
|
+
const origin = side.value === "top" || side.value === "bottom" ? `${cross} ${ORIGIN[side.value]}` : `${ORIGIN[side.value]} ${cross}`;
|
|
505
|
+
const dir = side.value === "top" ? 1 : side.value === "bottom" ? -1 : 0;
|
|
506
|
+
return {
|
|
507
|
+
"position": "fixed",
|
|
508
|
+
"left": `${x.value}px`,
|
|
509
|
+
"top": `${y.value}px`,
|
|
510
|
+
"transformOrigin": origin,
|
|
511
|
+
"minWidth": read().matchWidth && referenceWidth.value ? `${referenceWidth.value}px` : void 0,
|
|
512
|
+
"--j-available-height": availableHeight.value ? `${availableHeight.value}px` : void 0,
|
|
513
|
+
"--j-pop-dir": String(dir),
|
|
514
|
+
"--j-pop-x": String(side.value === "left" ? 1 : side.value === "right" ? -1 : 0)
|
|
515
|
+
};
|
|
516
|
+
}),
|
|
517
|
+
update
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
//#endregion
|
|
521
|
+
//#region src/composables/useTypeahead.ts
|
|
522
|
+
/**
|
|
523
|
+
* Type-to-jump for lists. Characters typed in quick succession form a query;
|
|
524
|
+
* repeating the same letter cycles through matches.
|
|
525
|
+
*/
|
|
526
|
+
function useTypeahead(timeout = 500) {
|
|
527
|
+
let query = "";
|
|
528
|
+
let timer;
|
|
529
|
+
function search(key, items, getLabel, currentIndex) {
|
|
530
|
+
if (key.length !== 1 || key === " " && query === "") return -1;
|
|
531
|
+
clearTimeout(timer);
|
|
532
|
+
timer = setTimeout(() => query = "", timeout);
|
|
533
|
+
query += key.toLowerCase();
|
|
534
|
+
const repeated = query.length > 1 && [...query].every((char) => char === query[0]);
|
|
535
|
+
const needle = repeated ? query[0] : query;
|
|
536
|
+
const start = repeated || query.length === 1 ? currentIndex + 1 : currentIndex;
|
|
537
|
+
for (let i = 0; i < items.length; i++) {
|
|
538
|
+
const index = (start + i + items.length) % items.length;
|
|
539
|
+
if (getLabel(items[index]).trim().toLowerCase().startsWith(needle)) return index;
|
|
540
|
+
}
|
|
541
|
+
return -1;
|
|
542
|
+
}
|
|
543
|
+
const isSearching = () => query.length > 0;
|
|
544
|
+
return {
|
|
545
|
+
search,
|
|
546
|
+
isSearching
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
//#endregion
|
|
550
|
+
//#region src/components/dropdown-menu/JMenuContent.vue?vue&type=script&setup=true&lang.ts
|
|
551
|
+
var _hoisted_1$2 = ["id", "aria-labelledby"];
|
|
552
|
+
//#endregion
|
|
553
|
+
//#region src/components/dropdown-menu/JMenuContent.vue
|
|
554
|
+
var JMenuContent_default = /* @__PURE__ */ defineComponent({
|
|
555
|
+
__name: "JMenuContent",
|
|
556
|
+
props: {
|
|
557
|
+
open: { type: Boolean },
|
|
558
|
+
reference: {},
|
|
559
|
+
id: {},
|
|
560
|
+
labelledby: {},
|
|
561
|
+
placement: {},
|
|
562
|
+
offset: {},
|
|
563
|
+
initialFocus: {},
|
|
564
|
+
isSub: { type: Boolean }
|
|
565
|
+
},
|
|
566
|
+
emits: [
|
|
567
|
+
"escape",
|
|
568
|
+
"outside",
|
|
569
|
+
"back",
|
|
570
|
+
"pointerenter"
|
|
571
|
+
],
|
|
572
|
+
setup(__props, { expose: __expose, emit: __emit }) {
|
|
573
|
+
/**
|
|
574
|
+
* Internal: the floating list shared by the root menu and submenus.
|
|
575
|
+
* Owns positioning, keyboard navigation, typeahead and the layer.
|
|
576
|
+
*/
|
|
577
|
+
const props = __props;
|
|
578
|
+
const emit = __emit;
|
|
579
|
+
const root = inject(MENU_ROOT);
|
|
580
|
+
const content = ref(null);
|
|
581
|
+
const { styles } = useFloating(toRef(props, "reference"), content, () => ({
|
|
582
|
+
placement: props.placement,
|
|
583
|
+
offset: props.offset
|
|
584
|
+
}));
|
|
585
|
+
const typeahead = useTypeahead();
|
|
586
|
+
useLayer({
|
|
587
|
+
active: toRef(props, "open"),
|
|
588
|
+
elements: () => [content.value, props.reference],
|
|
589
|
+
onEscape: () => emit("escape"),
|
|
590
|
+
onPointerDownOutside: (event) => emit("outside", event)
|
|
591
|
+
});
|
|
592
|
+
const openSub = ref(null);
|
|
593
|
+
let subTimer;
|
|
594
|
+
provide(MENU_LEVEL, {
|
|
595
|
+
content,
|
|
596
|
+
openSub,
|
|
597
|
+
setOpenSub: (id) => {
|
|
598
|
+
clearTimeout(subTimer);
|
|
599
|
+
openSub.value = id;
|
|
600
|
+
},
|
|
601
|
+
scheduleSubClose: () => {
|
|
602
|
+
if (!openSub.value) return;
|
|
603
|
+
clearTimeout(subTimer);
|
|
604
|
+
subTimer = setTimeout(() => openSub.value = null, 180);
|
|
605
|
+
},
|
|
606
|
+
cancelSubClose: () => clearTimeout(subTimer)
|
|
607
|
+
});
|
|
608
|
+
watch(() => props.open, async (value) => {
|
|
609
|
+
if (!value) {
|
|
610
|
+
openSub.value = null;
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (props.initialFocus === "none") return;
|
|
614
|
+
await nextTick();
|
|
615
|
+
const items = getMenuItems(content.value);
|
|
616
|
+
if (props.initialFocus === "first") focusElement(items[0] ?? content.value);
|
|
617
|
+
else if (props.initialFocus === "last") focusElement(items[items.length - 1] ?? content.value);
|
|
618
|
+
else focusElement(content.value);
|
|
619
|
+
}, { flush: "post" });
|
|
620
|
+
function move(delta) {
|
|
621
|
+
const items = getMenuItems(content.value);
|
|
622
|
+
if (items.length === 0) return;
|
|
623
|
+
const current = items.indexOf(document.activeElement);
|
|
624
|
+
let next;
|
|
625
|
+
if (delta === "first") next = 0;
|
|
626
|
+
else if (delta === "last") next = items.length - 1;
|
|
627
|
+
else if (current === -1) next = delta === 1 ? 0 : items.length - 1;
|
|
628
|
+
else next = (current + delta + items.length) % items.length;
|
|
629
|
+
focusElement(items[next]);
|
|
630
|
+
}
|
|
631
|
+
function onKeydown(event) {
|
|
632
|
+
const { key } = event;
|
|
633
|
+
switch (key) {
|
|
634
|
+
case "ArrowDown":
|
|
635
|
+
event.preventDefault();
|
|
636
|
+
move(1);
|
|
637
|
+
break;
|
|
638
|
+
case "ArrowUp":
|
|
639
|
+
event.preventDefault();
|
|
640
|
+
move(-1);
|
|
641
|
+
break;
|
|
642
|
+
case "Home":
|
|
643
|
+
case "PageUp":
|
|
644
|
+
event.preventDefault();
|
|
645
|
+
move("first");
|
|
646
|
+
break;
|
|
647
|
+
case "End":
|
|
648
|
+
case "PageDown":
|
|
649
|
+
event.preventDefault();
|
|
650
|
+
move("last");
|
|
651
|
+
break;
|
|
652
|
+
case "ArrowLeft":
|
|
653
|
+
if (props.isSub) {
|
|
654
|
+
event.preventDefault();
|
|
655
|
+
emit("back");
|
|
656
|
+
}
|
|
657
|
+
break;
|
|
658
|
+
case "Tab":
|
|
659
|
+
event.preventDefault();
|
|
660
|
+
root.close(true);
|
|
661
|
+
break;
|
|
662
|
+
case "Enter":
|
|
663
|
+
case " ": {
|
|
664
|
+
const active = document.activeElement;
|
|
665
|
+
if (active && active !== content.value && content.value?.contains(active)) {
|
|
666
|
+
event.preventDefault();
|
|
667
|
+
active.click();
|
|
668
|
+
}
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
default: if (key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) {
|
|
672
|
+
const items = getMenuItems(content.value);
|
|
673
|
+
const index = typeahead.search(key, items, (item) => item.textContent ?? "", items.indexOf(document.activeElement));
|
|
674
|
+
if (index >= 0) focusElement(items[index]);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
__expose({ content });
|
|
679
|
+
return (_ctx, _cache) => {
|
|
680
|
+
return openBlock(), createBlock(JPortal_default, null, {
|
|
681
|
+
default: withCtx(() => [createVNode(Transition, { name: "j-pop" }, {
|
|
682
|
+
default: withCtx(() => [__props.open ? (openBlock(), createElementBlock("div", {
|
|
683
|
+
key: 0,
|
|
684
|
+
id: __props.id,
|
|
685
|
+
ref_key: "content",
|
|
686
|
+
ref: content,
|
|
687
|
+
role: "menu",
|
|
688
|
+
tabindex: "-1",
|
|
689
|
+
class: normalizeClass(["j-menu j-popover-surface", { "j-menu--sub": __props.isSub }]),
|
|
690
|
+
style: normalizeStyle(unref(styles)),
|
|
691
|
+
"aria-labelledby": __props.labelledby,
|
|
692
|
+
"aria-orientation": "vertical",
|
|
693
|
+
onKeydown,
|
|
694
|
+
onPointerenter: _cache[0] || (_cache[0] = ($event) => emit("pointerenter"))
|
|
695
|
+
}, [renderSlot(_ctx.$slots, "default")], 46, _hoisted_1$2)) : createCommentVNode("", true)]),
|
|
696
|
+
_: 3
|
|
697
|
+
})]),
|
|
698
|
+
_: 3
|
|
699
|
+
});
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
//#endregion
|
|
704
|
+
//#region src/components/dropdown-menu/JDropdownMenu.vue
|
|
705
|
+
var JDropdownMenu_default = /* @__PURE__ */ defineComponent({
|
|
706
|
+
__name: "JDropdownMenu",
|
|
707
|
+
props: /*@__PURE__*/ mergeModels({
|
|
708
|
+
placement: { default: "bottom-start" },
|
|
709
|
+
offset: { default: 6 },
|
|
710
|
+
disabled: { type: Boolean }
|
|
711
|
+
}, {
|
|
712
|
+
"open": {
|
|
713
|
+
type: Boolean,
|
|
714
|
+
default: false
|
|
715
|
+
},
|
|
716
|
+
"openModifiers": {}
|
|
717
|
+
}),
|
|
718
|
+
emits: ["update:open"],
|
|
719
|
+
setup(__props, { expose: __expose }) {
|
|
720
|
+
const props = __props;
|
|
721
|
+
const open = useModel(__props, "open");
|
|
722
|
+
const id = useId();
|
|
723
|
+
const triggerId = `j-${id}-trigger`;
|
|
724
|
+
const menuId = `j-${id}-menu`;
|
|
725
|
+
const triggerRef = ref();
|
|
726
|
+
const triggerEl = computed(() => unrefElement(triggerRef.value));
|
|
727
|
+
const initialFocus = ref("content");
|
|
728
|
+
function show(focus) {
|
|
729
|
+
if (props.disabled) return;
|
|
730
|
+
initialFocus.value = focus;
|
|
731
|
+
open.value = true;
|
|
732
|
+
}
|
|
733
|
+
function close(restoreFocus = true) {
|
|
734
|
+
if (!open.value) return;
|
|
735
|
+
open.value = false;
|
|
736
|
+
if (restoreFocus) nextTick(() => focusElement(triggerEl.value));
|
|
737
|
+
}
|
|
738
|
+
provide(MENU_ROOT, { close });
|
|
739
|
+
function onClick(event) {
|
|
740
|
+
if (open.value) return close(false);
|
|
741
|
+
show(event.detail === 0 ? "first" : "content");
|
|
742
|
+
}
|
|
743
|
+
function onKeydown(event) {
|
|
744
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
745
|
+
event.preventDefault();
|
|
746
|
+
show(event.key === "ArrowDown" ? "first" : "last");
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
const triggerProps = computed(() => ({
|
|
750
|
+
"id": triggerId,
|
|
751
|
+
"aria-haspopup": "menu",
|
|
752
|
+
"aria-expanded": open.value,
|
|
753
|
+
"aria-controls": open.value ? menuId : void 0,
|
|
754
|
+
"data-state": open.value ? "open" : "closed",
|
|
755
|
+
"onClick": onClick,
|
|
756
|
+
"onKeydown": onKeydown
|
|
757
|
+
}));
|
|
758
|
+
__expose({
|
|
759
|
+
open: () => show("content"),
|
|
760
|
+
close
|
|
761
|
+
});
|
|
762
|
+
return (_ctx, _cache) => {
|
|
763
|
+
return openBlock(), createElementBlock(Fragment, null, [createVNode(unref(JSlot), mergeProps({
|
|
764
|
+
ref_key: "triggerRef",
|
|
765
|
+
ref: triggerRef
|
|
766
|
+
}, triggerProps.value), {
|
|
767
|
+
default: withCtx(() => [renderSlot(_ctx.$slots, "trigger", { open: open.value })]),
|
|
768
|
+
_: 3
|
|
769
|
+
}, 16), createVNode(JMenuContent_default, {
|
|
770
|
+
id: menuId,
|
|
771
|
+
open: open.value,
|
|
772
|
+
reference: triggerEl.value,
|
|
773
|
+
labelledby: triggerEl.value?.id || triggerId,
|
|
774
|
+
placement: __props.placement,
|
|
775
|
+
offset: __props.offset,
|
|
776
|
+
"initial-focus": initialFocus.value,
|
|
777
|
+
onEscape: _cache[0] || (_cache[0] = ($event) => close(true)),
|
|
778
|
+
onOutside: _cache[1] || (_cache[1] = ($event) => close(false))
|
|
779
|
+
}, {
|
|
780
|
+
default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
|
|
781
|
+
_: 3
|
|
782
|
+
}, 8, [
|
|
783
|
+
"open",
|
|
784
|
+
"reference",
|
|
785
|
+
"labelledby",
|
|
786
|
+
"placement",
|
|
787
|
+
"offset",
|
|
788
|
+
"initial-focus"
|
|
789
|
+
])], 64);
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
//#endregion
|
|
794
|
+
//#region src/components/dropdown-menu/JDropdownMenuItem.vue?vue&type=script&setup=true&lang.ts
|
|
795
|
+
var _hoisted_1$1 = {
|
|
796
|
+
key: 0,
|
|
797
|
+
class: "j-menu__icon"
|
|
798
|
+
};
|
|
799
|
+
var _hoisted_2$1 = { class: "j-menu__text" };
|
|
800
|
+
var _hoisted_3$1 = {
|
|
801
|
+
key: 1,
|
|
802
|
+
class: "j-menu__shortcut"
|
|
803
|
+
};
|
|
804
|
+
//#endregion
|
|
805
|
+
//#region src/components/dropdown-menu/JDropdownMenuItem.vue
|
|
806
|
+
var JDropdownMenuItem_default = /* @__PURE__ */ defineComponent({
|
|
807
|
+
__name: "JDropdownMenuItem",
|
|
808
|
+
props: {
|
|
809
|
+
disabled: { type: Boolean },
|
|
810
|
+
destructive: { type: Boolean },
|
|
811
|
+
shortcut: {},
|
|
812
|
+
as: { default: "div" },
|
|
813
|
+
checked: {
|
|
814
|
+
type: Boolean,
|
|
815
|
+
default: void 0
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
emits: ["select"],
|
|
819
|
+
setup(__props, { emit: __emit }) {
|
|
820
|
+
const props = __props;
|
|
821
|
+
const emit = __emit;
|
|
822
|
+
const root = inject(MENU_ROOT);
|
|
823
|
+
const level = inject(MENU_LEVEL);
|
|
824
|
+
const el = ref(null);
|
|
825
|
+
function onClick(event) {
|
|
826
|
+
if (props.disabled) {
|
|
827
|
+
event.preventDefault();
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
const selectEvent = new Event("select", { cancelable: true });
|
|
831
|
+
emit("select", selectEvent);
|
|
832
|
+
if (!selectEvent.defaultPrevented) root.close(true);
|
|
833
|
+
}
|
|
834
|
+
function resolve(target) {
|
|
835
|
+
const value = target;
|
|
836
|
+
return value instanceof HTMLElement ? value : value?.$el ?? null;
|
|
837
|
+
}
|
|
838
|
+
function onPointerMove() {
|
|
839
|
+
level.scheduleSubClose();
|
|
840
|
+
const node = resolve(el.value);
|
|
841
|
+
if (props.disabled || !node || document.activeElement === node) return;
|
|
842
|
+
focusElement(node);
|
|
843
|
+
}
|
|
844
|
+
function onPointerLeave() {
|
|
845
|
+
const node = resolve(el.value);
|
|
846
|
+
if (node && document.activeElement === node) focusElement(level.content.value);
|
|
847
|
+
}
|
|
848
|
+
return (_ctx, _cache) => {
|
|
849
|
+
return openBlock(), createBlock(resolveDynamicComponent(__props.as), {
|
|
850
|
+
ref_key: "el",
|
|
851
|
+
ref: el,
|
|
852
|
+
role: __props.checked === void 0 ? "menuitem" : "menuitemradio",
|
|
853
|
+
"aria-checked": __props.checked === void 0 ? void 0 : __props.checked,
|
|
854
|
+
tabindex: "-1",
|
|
855
|
+
class: normalizeClass(["j-menu__item", { "j-menu__item--destructive": __props.destructive }]),
|
|
856
|
+
"aria-disabled": __props.disabled || void 0,
|
|
857
|
+
onClick,
|
|
858
|
+
onPointermove: onPointerMove,
|
|
859
|
+
onPointerleave: onPointerLeave
|
|
860
|
+
}, {
|
|
861
|
+
default: withCtx(() => [
|
|
862
|
+
_ctx.$slots.leading ? (openBlock(), createElementBlock("span", _hoisted_1$1, [renderSlot(_ctx.$slots, "leading")])) : createCommentVNode("", true),
|
|
863
|
+
createElementVNode("span", _hoisted_2$1, [renderSlot(_ctx.$slots, "default")]),
|
|
864
|
+
__props.shortcut ? (openBlock(), createElementBlock("span", _hoisted_3$1, toDisplayString(__props.shortcut), 1)) : createCommentVNode("", true),
|
|
865
|
+
__props.checked !== void 0 ? (openBlock(), createBlock(unref(IconCheck), {
|
|
866
|
+
key: 2,
|
|
867
|
+
class: normalizeClass(["j-menu__check", { "is-checked": __props.checked }])
|
|
868
|
+
}, null, 8, ["class"])) : createCommentVNode("", true)
|
|
869
|
+
]),
|
|
870
|
+
_: 3
|
|
871
|
+
}, 40, [
|
|
872
|
+
"role",
|
|
873
|
+
"aria-checked",
|
|
874
|
+
"class",
|
|
875
|
+
"aria-disabled"
|
|
876
|
+
]);
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
//#endregion
|
|
881
|
+
//#region src/components/field/JFieldShell.vue?vue&type=script&setup=true&lang.ts
|
|
882
|
+
var _hoisted_1 = {
|
|
883
|
+
key: 0,
|
|
884
|
+
class: "j-field__required",
|
|
885
|
+
"aria-hidden": "true"
|
|
886
|
+
};
|
|
887
|
+
var _hoisted_2 = { class: "j-field__messages" };
|
|
888
|
+
var _hoisted_3 = ["id"];
|
|
889
|
+
var _hoisted_4 = ["id"];
|
|
890
|
+
//#endregion
|
|
891
|
+
//#region src/components/field/JFieldShell.vue
|
|
892
|
+
var JFieldShell_default = /* @__PURE__ */ defineComponent({
|
|
893
|
+
__name: "JFieldShell",
|
|
894
|
+
props: {
|
|
895
|
+
id: {},
|
|
896
|
+
labelId: {},
|
|
897
|
+
descriptionId: {},
|
|
898
|
+
errorId: {},
|
|
899
|
+
label: {},
|
|
900
|
+
description: {},
|
|
901
|
+
errorMessage: {},
|
|
902
|
+
showDescription: { type: Boolean },
|
|
903
|
+
required: { type: Boolean },
|
|
904
|
+
disabled: { type: Boolean },
|
|
905
|
+
labelFor: {}
|
|
906
|
+
},
|
|
907
|
+
setup(__props) {
|
|
908
|
+
/**
|
|
909
|
+
* Internal: lays out label, control, description and error for form fields.
|
|
910
|
+
* Not exported | every field component composes it.
|
|
911
|
+
*/
|
|
912
|
+
return (_ctx, _cache) => {
|
|
913
|
+
return openBlock(), createElementBlock("div", { class: normalizeClass(["j-field", { "is-disabled": __props.disabled }]) }, [
|
|
914
|
+
__props.label || _ctx.$slots.label ? (openBlock(), createBlock(resolveDynamicComponent(__props.labelFor === null ? "span" : "label"), {
|
|
915
|
+
key: 0,
|
|
916
|
+
id: __props.labelId,
|
|
917
|
+
class: "j-field__label",
|
|
918
|
+
for: __props.labelFor === null ? void 0 : __props.labelFor ?? __props.id
|
|
919
|
+
}, {
|
|
920
|
+
default: withCtx(() => [renderSlot(_ctx.$slots, "label", {}, () => [createTextVNode(toDisplayString(__props.label), 1)]), __props.required ? (openBlock(), createElementBlock("span", _hoisted_1, "*")) : createCommentVNode("", true)]),
|
|
921
|
+
_: 3
|
|
922
|
+
}, 8, ["id", "for"])) : createCommentVNode("", true),
|
|
923
|
+
renderSlot(_ctx.$slots, "default"),
|
|
924
|
+
createElementVNode("div", _hoisted_2, [createVNode(Transition, {
|
|
925
|
+
name: "j-field-message",
|
|
926
|
+
mode: "out-in"
|
|
927
|
+
}, {
|
|
928
|
+
default: withCtx(() => [__props.errorMessage ? (openBlock(), createElementBlock("p", {
|
|
929
|
+
id: __props.errorId,
|
|
930
|
+
key: __props.errorMessage,
|
|
931
|
+
class: "j-field__error"
|
|
932
|
+
}, toDisplayString(__props.errorMessage), 9, _hoisted_3)) : __props.showDescription ? (openBlock(), createElementBlock("p", {
|
|
933
|
+
key: 1,
|
|
934
|
+
id: __props.descriptionId,
|
|
935
|
+
class: "j-field__description"
|
|
936
|
+
}, toDisplayString(__props.description), 9, _hoisted_4)) : createCommentVNode("", true)]),
|
|
937
|
+
_: 1
|
|
938
|
+
})])
|
|
939
|
+
], 2);
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
});
|
|
943
|
+
//#endregion
|
|
944
|
+
//#region src/components/field/useField.ts
|
|
945
|
+
/** Ids and ARIA wiring shared by every form control. */
|
|
946
|
+
function useField(props) {
|
|
947
|
+
const generated = useId();
|
|
948
|
+
const p = () => toValue(props);
|
|
949
|
+
const id = computed(() => p().id ?? `j-${generated}`);
|
|
950
|
+
const labelId = computed(() => `${id.value}-label`);
|
|
951
|
+
const descriptionId = computed(() => `${id.value}-description`);
|
|
952
|
+
const errorId = computed(() => `${id.value}-error`);
|
|
953
|
+
const invalid = computed(() => !!p().error);
|
|
954
|
+
const errorMessage = computed(() => typeof p().error === "string" ? p().error : "");
|
|
955
|
+
return {
|
|
956
|
+
id,
|
|
957
|
+
labelId,
|
|
958
|
+
descriptionId,
|
|
959
|
+
errorId,
|
|
960
|
+
invalid,
|
|
961
|
+
errorMessage,
|
|
962
|
+
showDescription: computed(() => !!p().description && !errorMessage.value),
|
|
963
|
+
describedBy: computed(() => {
|
|
964
|
+
const ids = [];
|
|
965
|
+
if (errorMessage.value) ids.push(errorId.value);
|
|
966
|
+
else if (p().description) ids.push(descriptionId.value);
|
|
967
|
+
return ids.length ? ids.join(" ") : void 0;
|
|
968
|
+
})
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
//#endregion
|
|
972
|
+
//#region src/components/kbd/JKbd.vue
|
|
973
|
+
var JKbd_default = /* @__PURE__ */ defineComponent({
|
|
974
|
+
__name: "JKbd",
|
|
975
|
+
props: {
|
|
976
|
+
keys: {},
|
|
977
|
+
size: { default: "md" }
|
|
978
|
+
},
|
|
979
|
+
setup(__props) {
|
|
980
|
+
const props = __props;
|
|
981
|
+
const mounted = ref(false);
|
|
982
|
+
onMounted(() => mounted.value = true);
|
|
983
|
+
const parts = computed(() => {
|
|
984
|
+
if (!props.keys) return [];
|
|
985
|
+
if (!mounted.value) return props.keys.split("+").map((part) => part === "mod" ? "Ctrl" : part.length === 1 ? part.toUpperCase() : part);
|
|
986
|
+
return formatHotkey(props.keys);
|
|
987
|
+
});
|
|
988
|
+
return (_ctx, _cache) => {
|
|
989
|
+
return __props.keys ? (openBlock(), createElementBlock("span", {
|
|
990
|
+
key: 0,
|
|
991
|
+
class: normalizeClass(["j-kbd-group", `j-kbd-group--${__props.size}`])
|
|
992
|
+
}, [(openBlock(true), createElementBlock(Fragment, null, renderList(parts.value, (part, i) => {
|
|
993
|
+
return openBlock(), createElementBlock("kbd", {
|
|
994
|
+
key: i,
|
|
995
|
+
class: "j-kbd"
|
|
996
|
+
}, toDisplayString(part), 1);
|
|
997
|
+
}), 128))], 2)) : (openBlock(), createElementBlock("kbd", {
|
|
998
|
+
key: 1,
|
|
999
|
+
class: normalizeClass(["j-kbd", { "j-kbd--sm": __props.size === "sm" }])
|
|
1000
|
+
}, [renderSlot(_ctx.$slots, "default")], 2));
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
//#endregion
|
|
1005
|
+
//#region src/components/tooltip/JTooltip.vue?vue&type=script&setup=true&lang.ts
|
|
1006
|
+
var warmUntil = 0;
|
|
1007
|
+
var WARM_WINDOW = 400;
|
|
1008
|
+
//#endregion
|
|
1009
|
+
//#region src/components/tooltip/JTooltip.vue
|
|
1010
|
+
var JTooltip_default = /* @__PURE__ */ defineComponent({
|
|
1011
|
+
__name: "JTooltip",
|
|
1012
|
+
props: {
|
|
1013
|
+
content: {},
|
|
1014
|
+
side: { default: "top" },
|
|
1015
|
+
align: { default: "center" },
|
|
1016
|
+
delay: { default: 450 },
|
|
1017
|
+
disabled: { type: Boolean }
|
|
1018
|
+
},
|
|
1019
|
+
setup(__props) {
|
|
1020
|
+
const props = __props;
|
|
1021
|
+
const id = `j-${useId()}-tooltip`;
|
|
1022
|
+
const open = ref(false);
|
|
1023
|
+
const triggerRef = ref();
|
|
1024
|
+
const triggerEl = computed(() => unrefElement(triggerRef.value));
|
|
1025
|
+
const floating = ref(null);
|
|
1026
|
+
const placement = computed(() => props.align === "center" ? props.side : `${props.side}-${props.align}`);
|
|
1027
|
+
const { styles } = useFloating(triggerEl, floating, () => ({
|
|
1028
|
+
placement: placement.value,
|
|
1029
|
+
offset: 6
|
|
1030
|
+
}));
|
|
1031
|
+
useLayer({
|
|
1032
|
+
active: open,
|
|
1033
|
+
elements: () => [triggerEl.value, floating.value],
|
|
1034
|
+
onEscape: () => hide(true)
|
|
1035
|
+
});
|
|
1036
|
+
let openTimer;
|
|
1037
|
+
let closeTimer;
|
|
1038
|
+
let suppressed = false;
|
|
1039
|
+
function show(immediate = false) {
|
|
1040
|
+
if (props.disabled || suppressed) return;
|
|
1041
|
+
clearTimeout(closeTimer);
|
|
1042
|
+
if (open.value) return;
|
|
1043
|
+
clearTimeout(openTimer);
|
|
1044
|
+
if (immediate || Date.now() < warmUntil) open.value = true;
|
|
1045
|
+
else openTimer = setTimeout(() => open.value = true, props.delay);
|
|
1046
|
+
}
|
|
1047
|
+
function hide(immediate = false) {
|
|
1048
|
+
clearTimeout(openTimer);
|
|
1049
|
+
clearTimeout(closeTimer);
|
|
1050
|
+
const close = () => {
|
|
1051
|
+
if (open.value) warmUntil = Date.now() + WARM_WINDOW;
|
|
1052
|
+
open.value = false;
|
|
1053
|
+
};
|
|
1054
|
+
if (immediate) close();
|
|
1055
|
+
else closeTimer = setTimeout(close, 80);
|
|
1056
|
+
}
|
|
1057
|
+
onBeforeUnmount(() => {
|
|
1058
|
+
clearTimeout(openTimer);
|
|
1059
|
+
clearTimeout(closeTimer);
|
|
1060
|
+
});
|
|
1061
|
+
const triggerProps = computed(() => ({
|
|
1062
|
+
"aria-describedby": props.disabled ? void 0 : id,
|
|
1063
|
+
"onPointerenter": (event) => event.pointerType === "mouse" && show(),
|
|
1064
|
+
"onPointerleave": (event) => {
|
|
1065
|
+
suppressed = false;
|
|
1066
|
+
if (event.pointerType === "mouse") hide();
|
|
1067
|
+
},
|
|
1068
|
+
"onPointerdown": () => {
|
|
1069
|
+
suppressed = true;
|
|
1070
|
+
hide(true);
|
|
1071
|
+
},
|
|
1072
|
+
"onFocus": (event) => {
|
|
1073
|
+
if (event.target.matches(":focus-visible")) show(true);
|
|
1074
|
+
},
|
|
1075
|
+
"onBlur": () => hide(true)
|
|
1076
|
+
}));
|
|
1077
|
+
return (_ctx, _cache) => {
|
|
1078
|
+
return openBlock(), createElementBlock(Fragment, null, [
|
|
1079
|
+
createVNode(unref(JSlot), mergeProps({
|
|
1080
|
+
ref_key: "triggerRef",
|
|
1081
|
+
ref: triggerRef
|
|
1082
|
+
}, triggerProps.value), {
|
|
1083
|
+
default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
|
|
1084
|
+
_: 3
|
|
1085
|
+
}, 16),
|
|
1086
|
+
!__props.disabled ? (openBlock(), createElementBlock("span", {
|
|
1087
|
+
key: 0,
|
|
1088
|
+
id,
|
|
1089
|
+
class: "j-sr-only"
|
|
1090
|
+
}, [renderSlot(_ctx.$slots, "content", {}, () => [createTextVNode(toDisplayString(__props.content), 1)])])) : createCommentVNode("", true),
|
|
1091
|
+
createVNode(JPortal_default, null, {
|
|
1092
|
+
default: withCtx(() => [createVNode(Transition, { name: "j-tooltip" }, {
|
|
1093
|
+
default: withCtx(() => [open.value ? (openBlock(), createElementBlock("div", {
|
|
1094
|
+
key: 0,
|
|
1095
|
+
ref_key: "floating",
|
|
1096
|
+
ref: floating,
|
|
1097
|
+
class: "j-tooltip",
|
|
1098
|
+
style: normalizeStyle(unref(styles)),
|
|
1099
|
+
"aria-hidden": "true",
|
|
1100
|
+
"data-juxt-layer-ignore": "",
|
|
1101
|
+
onPointerenter: _cache[0] || (_cache[0] = ($event) => show(true)),
|
|
1102
|
+
onPointerleave: _cache[1] || (_cache[1] = ($event) => hide())
|
|
1103
|
+
}, [renderSlot(_ctx.$slots, "content", {}, () => [createTextVNode(toDisplayString(__props.content), 1)])], 36)) : createCommentVNode("", true)]),
|
|
1104
|
+
_: 3
|
|
1105
|
+
})]),
|
|
1106
|
+
_: 3
|
|
1107
|
+
})
|
|
1108
|
+
], 64);
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
//#endregion
|
|
1113
|
+
export { IconCircleX as A, unrefElement as B, IconArrowUpRight as C, IconChevronRight as D, IconChevronLeft as E, Spinner as F, focusElement as I, getFocusableElements as L, IconMoreHorizontal as M, IconSearch as N, IconChevronUpDown as O, IconX as P, isClient as R, IconArrowDownRight as S, IconChevronDown as T, useLayer as _, JDropdownMenuItem_default as a, useHotkey as b, useTypeahead as c, MENU_ROOT as d, getMenuItems as f, splitByRanges as g, matchText as h, JFieldShell_default as i, IconInfo as j, IconCircleCheck as k, useFloating as l, JPortal_default as m, JKbd_default as n, JDropdownMenu_default as o, JSlot as p, useField as r, JMenuContent_default as s, JTooltip_default as t, MENU_LEVEL as u, formatHotkey as v, IconCheck as w, IconAlert as x, matchesHotkey as y, prefersReducedMotion as z };
|
|
1114
|
+
|
|
1115
|
+
//# sourceMappingURL=JTooltip-CAL9lsPF.js.map
|