@scalar/sidebar 0.8.8 → 0.8.10
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/dist/components/ScalarSidebar.vue.d.ts +2 -2
- package/dist/components/SidebarItem.vue.d.ts +2 -2
- package/dist/components/SidebarItemLabel.vue.d.ts +1 -1
- package/dist/helpers/filter-items.d.ts +1 -1
- package/dist/helpers/has-children.d.ts +1 -1
- package/dist/helpers/is-sidebar-folder.d.ts +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +832 -20
- package/dist/index.js.map +1 -0
- package/dist/style.css +4535 -1
- package/package.json +12 -18
- package/dist/_virtual/_plugin-vue_export-helper.js +0 -9
- package/dist/components/HttpMethod.vue.js +0 -30
- package/dist/components/HttpMethod.vue2.js +0 -4
- package/dist/components/ScalarSidebar.vue.js +0 -82
- package/dist/components/ScalarSidebar.vue2.js +0 -4
- package/dist/components/SidebarHttpBadge.vue.js +0 -7
- package/dist/components/SidebarHttpBadge.vue2.js +0 -41
- package/dist/components/SidebarItem.vue.js +0 -264
- package/dist/components/SidebarItem.vue2.js +0 -4
- package/dist/components/SidebarItemDecorator.vue.js +0 -12
- package/dist/components/SidebarItemLabel.vue.js +0 -22
- package/dist/components/SidebarItemLabel.vue2.js +0 -4
- package/dist/helpers/create-sidebar-state.js +0 -38
- package/dist/helpers/filter-items.js +0 -4
- package/dist/helpers/generate-reverse-index.js +0 -14
- package/dist/helpers/get-child-entry.js +0 -14
- package/dist/helpers/has-children.js +0 -4
- package/dist/helpers/is-sidebar-folder.js +0 -5
- package/dist/helpers/scroll-sidebar-to-top.js +0 -27
- package/dist/hooks/use-draggable.js +0 -82
package/dist/index.js
CHANGED
|
@@ -1,21 +1,833 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, mergeProps, normalizeClass, normalizeStyle, openBlock, ref, renderList, renderSlot, resolveComponent, resolveDynamicComponent, toDisplayString, toHandlers, toValue, unref, useSlots, withCtx } from "vue";
|
|
2
|
+
import { getHttpMethodInfo } from "@scalar/helpers/http/http-info";
|
|
3
|
+
import { normalizeHttpMethod } from "@scalar/helpers/http/normalize-http-method";
|
|
4
|
+
import { ScalarSidebar, ScalarSidebarGroup, ScalarSidebarItem, ScalarSidebarItems, ScalarSidebarSection, ScalarWrappingText } from "@scalar/components";
|
|
5
|
+
import { LibraryIcon } from "@scalar/icons/library";
|
|
6
|
+
import { cva } from "@scalar/use-hooks/useBindCx";
|
|
7
|
+
import { ScalarIconWebhooksLogo } from "@scalar/icons";
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/components/HttpMethod.vue
|
|
10
|
+
var HttpMethod_default = /* @__PURE__ */ defineComponent({
|
|
11
|
+
__name: "HttpMethod",
|
|
12
|
+
props: {
|
|
13
|
+
as: {},
|
|
14
|
+
property: {},
|
|
15
|
+
short: { type: Boolean },
|
|
16
|
+
method: {}
|
|
17
|
+
},
|
|
18
|
+
setup(__props) {
|
|
19
|
+
const props = __props;
|
|
20
|
+
/** Grabs the method info object which contains abbreviation, color, and background color etc */
|
|
21
|
+
const httpMethodInfo = computed(() => getHttpMethodInfo(String(props.method || "")));
|
|
22
|
+
/** Full method name */
|
|
23
|
+
const normalized = computed(() => normalizeHttpMethod(props.method));
|
|
24
|
+
return (_ctx, _cache) => {
|
|
25
|
+
return openBlock(), createBlock(resolveDynamicComponent(__props.as ?? "span"), {
|
|
26
|
+
class: "uppercase",
|
|
27
|
+
style: normalizeStyle({ [__props.property || "color"]: httpMethodInfo.value.colorVar })
|
|
28
|
+
}, {
|
|
29
|
+
default: withCtx(() => [renderSlot(_ctx.$slots, "default"), createTextVNode(" " + toDisplayString(__props.short ? httpMethodInfo.value.short : normalized.value), 1)]),
|
|
30
|
+
_: 3
|
|
31
|
+
}, 8, ["style"]);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/helpers/filter-items.ts
|
|
37
|
+
/** The type of items we want to */
|
|
38
|
+
var API_CLIENT_TYPES_SET = new Set([
|
|
39
|
+
"document",
|
|
40
|
+
"operation",
|
|
41
|
+
"example",
|
|
42
|
+
"tag"
|
|
43
|
+
]);
|
|
44
|
+
var filterItems = (layout, items) => {
|
|
45
|
+
if (layout === "reference") return items;
|
|
46
|
+
return items.filter((c) => API_CLIENT_TYPES_SET.has(c.type));
|
|
21
47
|
};
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region \0plugin-vue:export-helper
|
|
50
|
+
var _plugin_vue_export_helper_default = (sfc, props) => {
|
|
51
|
+
const target = sfc.__vccOpts || sfc;
|
|
52
|
+
for (const [key, val] of props) target[key] = val;
|
|
53
|
+
return target;
|
|
54
|
+
};
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/components/SidebarItemDecorator.vue
|
|
57
|
+
var _sfc_main = {};
|
|
58
|
+
var _hoisted_1$1 = { class: "bg-b-2 absolute top-[1lh] right-0.75 flex -translate-y-1/2 rounded border p-0.25 opacity-0 peer-hover/button:opacity-100 peer-focus-visible/button:opacity-100 focus-within:opacity-100 hover:opacity-100 has-[&[aria-expanded=true]]:opacity-100" };
|
|
59
|
+
function _sfc_render(_ctx, _cache) {
|
|
60
|
+
return openBlock(), createElementBlock("div", _hoisted_1$1, [renderSlot(_ctx.$slots, "default")]);
|
|
61
|
+
}
|
|
62
|
+
var SidebarItemDecorator_default = /* @__PURE__ */ _plugin_vue_export_helper_default(_sfc_main, [["render", _sfc_render]]);
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/helpers/has-children.ts
|
|
65
|
+
/**
|
|
66
|
+
* Type guard to check if the given Item has a non-empty array of children.
|
|
67
|
+
* Returns true if `currentItem` has a `children` property that is an array with at least one element.
|
|
68
|
+
*/
|
|
69
|
+
var hasChildren = (currentItem) => {
|
|
70
|
+
return "children" in currentItem && Array.isArray(currentItem.children) && currentItem.children.length > 0;
|
|
71
|
+
};
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/helpers/is-sidebar-folder.ts
|
|
74
|
+
/**
|
|
75
|
+
* Determines if a sidebar item should be treated as a "folder",
|
|
76
|
+
* i.e. a collapsible group, depending on the layout, its type,
|
|
77
|
+
* whether it has children, and special slot rules.
|
|
78
|
+
*
|
|
79
|
+
* @param layout - The sidebar layout ('client' or 'reference')
|
|
80
|
+
* @param item - The sidebar item to check
|
|
81
|
+
* @param hasEmptySlot - True if there is an empty slot to display as a group (client layout only)
|
|
82
|
+
* @returns True if the item should be rendered as a folder/group node
|
|
83
|
+
*/
|
|
84
|
+
var isSidebarFolder = (layout, item, hasEmptySlot) => {
|
|
85
|
+
if (!hasChildren(item)) {
|
|
86
|
+
if (layout === "client" && hasEmptySlot) return item.type === "document" || item.type === "tag";
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
if (layout === "client") return true;
|
|
90
|
+
if (layout === "reference") return item.type !== "operation" && item.type !== "webhook";
|
|
91
|
+
return false;
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/hooks/use-draggable.ts
|
|
95
|
+
/**
|
|
96
|
+
* Simple throttle function to avoid package dependencies
|
|
97
|
+
*/
|
|
98
|
+
var throttle = (callback, limit) => {
|
|
99
|
+
let wait = false;
|
|
100
|
+
return (...args) => {
|
|
101
|
+
if (wait) return;
|
|
102
|
+
callback(...args);
|
|
103
|
+
wait = true;
|
|
104
|
+
setTimeout(() => wait = false, limit);
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
/** Draggable class variants to apply to the draggable element */
|
|
108
|
+
var draggableVariants = cva({
|
|
109
|
+
base: "relative after:absolute after:inset-x-0 after:block after:bg-blue after:opacity-15 after:pointer-events-none after:rounded",
|
|
110
|
+
variants: { position: {
|
|
111
|
+
before: "after:-top-0.5 after:h-0.75",
|
|
112
|
+
after: "after:-bottom-0.5 after:h-0.75",
|
|
113
|
+
into: "after:inset-0"
|
|
114
|
+
} }
|
|
115
|
+
});
|
|
116
|
+
/**
|
|
117
|
+
* Shared state for drag and drop operations
|
|
118
|
+
* These are module-level refs so all draggable instances share the same state
|
|
119
|
+
*/
|
|
120
|
+
var draggingItem = ref(null);
|
|
121
|
+
var hoveredItem = ref(null);
|
|
122
|
+
/**
|
|
123
|
+
* Composable for handling drag and drop functionality
|
|
124
|
+
*/
|
|
125
|
+
function useDraggable(options) {
|
|
126
|
+
const { ceiling = .8, floor = .2, isDraggable = true, isDroppable = true, parentIds = [], id, onDragStart, onDragEnd } = options;
|
|
127
|
+
const parentId = computed(() => parentIds.at(-1) ?? null);
|
|
128
|
+
/** Check if isDroppable guard */
|
|
129
|
+
const _isDroppable = (offset) => typeof isDroppable === "function" ? isDroppable(draggingItem.value, {
|
|
130
|
+
id,
|
|
131
|
+
parentId: parentId.value,
|
|
132
|
+
offset
|
|
133
|
+
}) : toValue(isDroppable);
|
|
134
|
+
const handleDragStart = (ev) => {
|
|
135
|
+
if (!toValue(isDraggable) || !ev.dataTransfer || !(ev.target instanceof HTMLElement)) return;
|
|
136
|
+
ev.target.setAttribute("data-dragging", "true");
|
|
137
|
+
ev.dataTransfer.dropEffect = "move";
|
|
138
|
+
ev.dataTransfer.effectAllowed = "move";
|
|
139
|
+
const item = {
|
|
140
|
+
id,
|
|
141
|
+
parentId: parentId.value
|
|
142
|
+
};
|
|
143
|
+
draggingItem.value = item;
|
|
144
|
+
onDragStart?.(item);
|
|
145
|
+
};
|
|
146
|
+
const handleDragOver = throttle((ev) => {
|
|
147
|
+
if (!draggingItem.value || draggingItem.value.id === id || parentIds.includes(draggingItem.value?.id ?? "")) return;
|
|
148
|
+
const previousOffset = hoveredItem.value?.offset;
|
|
149
|
+
const height = ev.target.offsetHeight;
|
|
150
|
+
const _floor = floor * height;
|
|
151
|
+
const _ceiling = ceiling * height;
|
|
152
|
+
let offset = null;
|
|
153
|
+
if (ev.offsetY <= 0 && previousOffset && previousOffset !== "after") offset = previousOffset;
|
|
154
|
+
else if (ev.offsetY <= _floor) offset = "before";
|
|
155
|
+
else if (ev.offsetY >= _ceiling) offset = "after";
|
|
156
|
+
else if (ev.offsetY > _floor && ev.offsetY < _ceiling) offset = "into";
|
|
157
|
+
if (!_isDroppable(offset)) return;
|
|
158
|
+
hoveredItem.value = {
|
|
159
|
+
id,
|
|
160
|
+
parentId: parentId.value,
|
|
161
|
+
offset
|
|
162
|
+
};
|
|
163
|
+
}, 25);
|
|
164
|
+
const handleDragEnd = () => {
|
|
165
|
+
if (!hoveredItem.value || !draggingItem.value) return;
|
|
166
|
+
const _draggingItem = { ...draggingItem.value };
|
|
167
|
+
const _hoveredItem = { ...hoveredItem.value };
|
|
168
|
+
draggingItem.value = null;
|
|
169
|
+
hoveredItem.value = null;
|
|
170
|
+
document.querySelectorAll("[data-dragging]").forEach((el) => el.removeAttribute("data-dragging"));
|
|
171
|
+
if (_draggingItem.id === _hoveredItem.id) return;
|
|
172
|
+
onDragEnd?.(_draggingItem, _hoveredItem);
|
|
173
|
+
};
|
|
174
|
+
const draggableClass = computed(() => {
|
|
175
|
+
const position = id === hoveredItem.value?.id ? hoveredItem.value.offset : void 0;
|
|
176
|
+
if (!position) return "";
|
|
177
|
+
return draggableVariants({ position });
|
|
178
|
+
});
|
|
179
|
+
return {
|
|
180
|
+
draggableAttrs: computed(() => ({
|
|
181
|
+
class: draggableClass.value || void 0,
|
|
182
|
+
draggable: toValue(isDraggable) ? true : void 0
|
|
183
|
+
})),
|
|
184
|
+
draggableEvents: {
|
|
185
|
+
dragend: handleDragEnd,
|
|
186
|
+
dragover: (ev) => {
|
|
187
|
+
ev.preventDefault();
|
|
188
|
+
ev.stopPropagation();
|
|
189
|
+
handleDragOver(ev);
|
|
190
|
+
},
|
|
191
|
+
dragstart: (ev) => {
|
|
192
|
+
ev.stopPropagation();
|
|
193
|
+
handleDragStart(ev);
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
draggingItem,
|
|
197
|
+
hoveredItem
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/components/SidebarHttpBadge.vue
|
|
202
|
+
var SidebarHttpBadge_default = /* @__PURE__ */ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
|
|
203
|
+
__name: "SidebarHttpBadge",
|
|
204
|
+
props: {
|
|
205
|
+
method: {},
|
|
206
|
+
active: { type: Boolean },
|
|
207
|
+
webhook: { type: Boolean }
|
|
208
|
+
},
|
|
209
|
+
setup(__props) {
|
|
210
|
+
return (_ctx, _cache) => {
|
|
211
|
+
return openBlock(), createBlock(HttpMethod_default, {
|
|
212
|
+
class: normalizeClass([
|
|
213
|
+
"sidebar-heading-type",
|
|
214
|
+
`sidebar-heading-type--${__props.method.toLowerCase()}`,
|
|
215
|
+
{ "sidebar-heading-type-active": __props.active }
|
|
216
|
+
]),
|
|
217
|
+
method: __props.method,
|
|
218
|
+
property: "--method-color",
|
|
219
|
+
short: ""
|
|
220
|
+
}, {
|
|
221
|
+
default: withCtx(() => [_cache[0] || (_cache[0] = createElementVNode("span", { class: "sr-only" }, "HTTP Method:\xA0", -1)), renderSlot(_ctx.$slots, "default", {}, () => [__props.webhook ? (openBlock(), createBlock(unref(ScalarIconWebhooksLogo), {
|
|
222
|
+
key: 0,
|
|
223
|
+
style: normalizeStyle({ color: unref(getHttpMethodInfo)(__props.method).colorVar }),
|
|
224
|
+
weight: "bold"
|
|
225
|
+
}, null, 8, ["style"])) : createCommentVNode("", true)], true)]),
|
|
226
|
+
_: 3
|
|
227
|
+
}, 8, ["class", "method"]);
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}), [["__scopeId", "data-v-1857170e"]]);
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/components/SidebarItemLabel.vue
|
|
233
|
+
var SidebarItemLabel_default = /* @__PURE__ */ defineComponent({
|
|
234
|
+
__name: "SidebarItemLabel",
|
|
235
|
+
props: {
|
|
236
|
+
item: {},
|
|
237
|
+
operationTitleSource: {}
|
|
238
|
+
},
|
|
239
|
+
setup(__props) {
|
|
240
|
+
return (_ctx, _cache) => {
|
|
241
|
+
return __props.item.type === "model" ? (openBlock(), createBlock(unref(ScalarWrappingText), {
|
|
242
|
+
key: 0,
|
|
243
|
+
preset: "property",
|
|
244
|
+
text: __props.item.title
|
|
245
|
+
}, null, 8, ["text"])) : (openBlock(), createBlock(unref(ScalarWrappingText), {
|
|
246
|
+
key: 1,
|
|
247
|
+
text: __props.operationTitleSource === "path" && "path" in __props.item ? __props.item.path : __props.item.title
|
|
248
|
+
}, null, 8, ["text"]));
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
//#endregion
|
|
253
|
+
//#region src/components/SidebarItem.vue?vue&type=script&setup=true&lang.ts
|
|
254
|
+
var _hoisted_1 = {
|
|
255
|
+
key: 0,
|
|
256
|
+
class: "line-through"
|
|
257
|
+
};
|
|
258
|
+
var _hoisted_2 = {
|
|
259
|
+
key: 0,
|
|
260
|
+
class: "line-through"
|
|
261
|
+
};
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/components/SidebarItem.vue
|
|
264
|
+
var SidebarItem_default = /* @__PURE__ */ defineComponent({
|
|
265
|
+
__name: "SidebarItem",
|
|
266
|
+
props: {
|
|
267
|
+
item: {},
|
|
268
|
+
layout: {},
|
|
269
|
+
isSelected: { type: Function },
|
|
270
|
+
isExpanded: { type: Function },
|
|
271
|
+
options: {},
|
|
272
|
+
isDraggable: {},
|
|
273
|
+
isDroppable: { type: Function }
|
|
274
|
+
},
|
|
275
|
+
emits: [
|
|
276
|
+
"selectItem",
|
|
277
|
+
"onDragEnd",
|
|
278
|
+
"toggleGroup"
|
|
279
|
+
],
|
|
280
|
+
setup(__props, { emit: __emit }) {
|
|
281
|
+
const emit = __emit;
|
|
282
|
+
const slots = useSlots();
|
|
283
|
+
const isGroup = (currentItem) => {
|
|
284
|
+
return "isGroup" in currentItem && currentItem.isGroup;
|
|
285
|
+
};
|
|
286
|
+
const isDeprecated = (currentItem) => {
|
|
287
|
+
return ("isDeprecated" in currentItem && currentItem.isDeprecated) ?? false;
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* Handle drag end event and bubble it up to parent.
|
|
291
|
+
*/
|
|
292
|
+
const onDragEnd = (draggingItem, hoveredItem) => {
|
|
293
|
+
emit("onDragEnd", draggingItem, hoveredItem);
|
|
294
|
+
};
|
|
295
|
+
const { draggableAttrs, draggableEvents } = useDraggable({
|
|
296
|
+
id: __props.item.id,
|
|
297
|
+
isDraggable: __props.isDraggable,
|
|
298
|
+
isDroppable: __props.isDroppable,
|
|
299
|
+
onDragEnd
|
|
300
|
+
});
|
|
301
|
+
return (_ctx, _cache) => {
|
|
302
|
+
const _component_SidebarItem = resolveComponent("SidebarItem", true);
|
|
303
|
+
return unref(hasChildren)(__props.item) && isGroup(__props.item) ? (openBlock(), createBlock(unref(ScalarSidebarSection), mergeProps({
|
|
304
|
+
key: 0,
|
|
305
|
+
"data-sidebar-id": __props.item.id
|
|
306
|
+
}, unref(draggableAttrs), toHandlers(unref(draggableEvents))), {
|
|
307
|
+
items: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(filterItems)(__props.layout, __props.item.children), (child) => {
|
|
308
|
+
return openBlock(), createBlock(_component_SidebarItem, {
|
|
309
|
+
key: child.id,
|
|
310
|
+
isDraggable: __props.isDraggable,
|
|
311
|
+
isDroppable: __props.isDroppable,
|
|
312
|
+
isExpanded: __props.isExpanded,
|
|
313
|
+
isSelected: __props.isSelected,
|
|
314
|
+
item: child,
|
|
315
|
+
layout: __props.layout,
|
|
316
|
+
options: __props.options,
|
|
317
|
+
onOnDragEnd: onDragEnd,
|
|
318
|
+
onSelectItem: _cache[0] || (_cache[0] = (id) => emit("selectItem", id)),
|
|
319
|
+
onToggleGroup: _cache[1] || (_cache[1] = (id) => emit("toggleGroup", id))
|
|
320
|
+
}, createSlots({ _: 2 }, [
|
|
321
|
+
slots.decorator ? {
|
|
322
|
+
name: "decorator",
|
|
323
|
+
fn: withCtx((slotProps) => [renderSlot(_ctx.$slots, "decorator", mergeProps({ ref_for: true }, slotProps))]),
|
|
324
|
+
key: "0"
|
|
325
|
+
} : void 0,
|
|
326
|
+
slots.empty ? {
|
|
327
|
+
name: "empty",
|
|
328
|
+
fn: withCtx((slotProps) => [renderSlot(_ctx.$slots, "empty", mergeProps({ ref_for: true }, slotProps))]),
|
|
329
|
+
key: "1"
|
|
330
|
+
} : void 0,
|
|
331
|
+
slots.icon ? {
|
|
332
|
+
name: "icon",
|
|
333
|
+
fn: withCtx((slotProps) => [renderSlot(_ctx.$slots, "icon", mergeProps({ ref_for: true }, slotProps))]),
|
|
334
|
+
key: "2"
|
|
335
|
+
} : void 0
|
|
336
|
+
]), 1032, [
|
|
337
|
+
"isDraggable",
|
|
338
|
+
"isDroppable",
|
|
339
|
+
"isExpanded",
|
|
340
|
+
"isSelected",
|
|
341
|
+
"item",
|
|
342
|
+
"layout",
|
|
343
|
+
"options"
|
|
344
|
+
]);
|
|
345
|
+
}), 128))]),
|
|
346
|
+
default: withCtx(() => [createTextVNode(toDisplayString(__props.item.title) + " ", 1)]),
|
|
347
|
+
_: 3
|
|
348
|
+
}, 16, ["data-sidebar-id"])) : unref(isSidebarFolder)(__props.layout, __props.item, slots.empty !== void 0) ? (openBlock(), createBlock(unref(ScalarSidebarGroup), mergeProps({
|
|
349
|
+
key: 1,
|
|
350
|
+
active: __props.isSelected(__props.item.id),
|
|
351
|
+
class: "relative",
|
|
352
|
+
controlled: "",
|
|
353
|
+
"data-sidebar-id": __props.item.id
|
|
354
|
+
}, unref(draggableAttrs), {
|
|
355
|
+
discrete: __props.layout === "reference" && __props.item.type === "text",
|
|
356
|
+
open: __props.isExpanded(__props.item.id)
|
|
357
|
+
}, toHandlers(unref(draggableEvents)), {
|
|
358
|
+
onClick: _cache[4] || (_cache[4] = () => emit("selectItem", __props.item.id)),
|
|
359
|
+
onToggle: _cache[5] || (_cache[5] = () => emit("toggleGroup", __props.item.id))
|
|
360
|
+
}), createSlots({
|
|
361
|
+
items: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(filterItems)(__props.layout, __props.item.children ?? []), (child) => {
|
|
362
|
+
return openBlock(), createBlock(_component_SidebarItem, {
|
|
363
|
+
key: child.id,
|
|
364
|
+
isDraggable: __props.isDraggable,
|
|
365
|
+
isDroppable: __props.isDroppable,
|
|
366
|
+
isExpanded: __props.isExpanded,
|
|
367
|
+
isSelected: __props.isSelected,
|
|
368
|
+
item: child,
|
|
369
|
+
layout: __props.layout,
|
|
370
|
+
options: __props.options,
|
|
371
|
+
parentIds: [],
|
|
372
|
+
onOnDragEnd: onDragEnd,
|
|
373
|
+
onSelectItem: _cache[2] || (_cache[2] = (id) => emit("selectItem", id)),
|
|
374
|
+
onToggleGroup: _cache[3] || (_cache[3] = (id) => emit("toggleGroup", id))
|
|
375
|
+
}, createSlots({ _: 2 }, [
|
|
376
|
+
slots.decorator ? {
|
|
377
|
+
name: "decorator",
|
|
378
|
+
fn: withCtx((slotProps) => [renderSlot(_ctx.$slots, "decorator", mergeProps({ ref_for: true }, slotProps))]),
|
|
379
|
+
key: "0"
|
|
380
|
+
} : void 0,
|
|
381
|
+
slots.empty ? {
|
|
382
|
+
name: "empty",
|
|
383
|
+
fn: withCtx((slotProps) => [renderSlot(_ctx.$slots, "empty", mergeProps({ ref_for: true }, slotProps))]),
|
|
384
|
+
key: "1"
|
|
385
|
+
} : void 0,
|
|
386
|
+
slots.icon ? {
|
|
387
|
+
name: "icon",
|
|
388
|
+
fn: withCtx((slotProps) => [renderSlot(_ctx.$slots, "icon", mergeProps({ ref_for: true }, slotProps))]),
|
|
389
|
+
key: "2"
|
|
390
|
+
} : void 0
|
|
391
|
+
]), 1032, [
|
|
392
|
+
"isDraggable",
|
|
393
|
+
"isDroppable",
|
|
394
|
+
"isExpanded",
|
|
395
|
+
"isSelected",
|
|
396
|
+
"item",
|
|
397
|
+
"layout",
|
|
398
|
+
"options"
|
|
399
|
+
]);
|
|
400
|
+
}), 128)), slots.empty && (__props.item.children?.length ?? 0) === 0 ? renderSlot(_ctx.$slots, "empty", {
|
|
401
|
+
key: 0,
|
|
402
|
+
item: __props.item
|
|
403
|
+
}) : createCommentVNode("", true)]),
|
|
404
|
+
default: withCtx(() => [isDeprecated(__props.item) ? (openBlock(), createElementBlock("span", _hoisted_1, [createVNode(SidebarItemLabel_default, {
|
|
405
|
+
item: __props.item,
|
|
406
|
+
operationTitleSource: __props.options?.operationTitleSource
|
|
407
|
+
}, null, 8, ["item", "operationTitleSource"])])) : (openBlock(), createBlock(SidebarItemLabel_default, {
|
|
408
|
+
key: 1,
|
|
409
|
+
item: __props.item,
|
|
410
|
+
operationTitleSource: __props.options?.operationTitleSource
|
|
411
|
+
}, null, 8, ["item", "operationTitleSource"]))]),
|
|
412
|
+
_: 2
|
|
413
|
+
}, [
|
|
414
|
+
__props.item.type === "document" ? {
|
|
415
|
+
name: "icon",
|
|
416
|
+
fn: withCtx(({ open }) => [renderSlot(_ctx.$slots, "icon", {
|
|
417
|
+
item: __props.item,
|
|
418
|
+
open
|
|
419
|
+
}, () => [createVNode(unref(LibraryIcon), {
|
|
420
|
+
class: "block",
|
|
421
|
+
src: "icon" in __props.item && __props.item.icon || "interface-content-folder"
|
|
422
|
+
}, null, 8, ["src"])])]),
|
|
423
|
+
key: "0"
|
|
424
|
+
} : void 0,
|
|
425
|
+
"method" in __props.item ? {
|
|
426
|
+
name: "aside",
|
|
427
|
+
fn: withCtx(() => [createVNode(SidebarHttpBadge_default, {
|
|
428
|
+
active: __props.isSelected(__props.item.id),
|
|
429
|
+
class: normalizeClass(["mr-1 ml-2 h-4 self-start", { "group-hover/button:opacity-0 group-focus-visible/button:opacity-0 group-has-[~*_[aria-expanded=true]]/button:opacity-0 group-has-[~*:focus-within]/button:opacity-0 group-has-[~*:hover]/button:opacity-0": slots.decorator }]),
|
|
430
|
+
method: __props.item.method,
|
|
431
|
+
webhook: __props.item.type === "webhook"
|
|
432
|
+
}, null, 8, [
|
|
433
|
+
"active",
|
|
434
|
+
"class",
|
|
435
|
+
"method",
|
|
436
|
+
"webhook"
|
|
437
|
+
])]),
|
|
438
|
+
key: "1"
|
|
439
|
+
} : void 0,
|
|
440
|
+
slots.decorator ? {
|
|
441
|
+
name: "after",
|
|
442
|
+
fn: withCtx(() => [createVNode(SidebarItemDecorator_default, null, {
|
|
443
|
+
default: withCtx(() => [renderSlot(_ctx.$slots, "decorator", { item: __props.item })]),
|
|
444
|
+
_: 3
|
|
445
|
+
})]),
|
|
446
|
+
key: "2"
|
|
447
|
+
} : void 0
|
|
448
|
+
]), 1040, [
|
|
449
|
+
"active",
|
|
450
|
+
"data-sidebar-id",
|
|
451
|
+
"discrete",
|
|
452
|
+
"open"
|
|
453
|
+
])) : (openBlock(), createBlock(unref(ScalarSidebarItem), mergeProps({
|
|
454
|
+
key: 2,
|
|
455
|
+
is: "button"
|
|
456
|
+
}, unref(draggableAttrs), {
|
|
457
|
+
class: "relative",
|
|
458
|
+
"data-sidebar-id": __props.item.id,
|
|
459
|
+
selected: __props.isSelected(__props.item.id)
|
|
460
|
+
}, toHandlers(unref(draggableEvents)), { onClick: _cache[6] || (_cache[6] = () => emit("selectItem", __props.item.id)) }), createSlots({
|
|
461
|
+
default: withCtx(() => [isDeprecated(__props.item) ? (openBlock(), createElementBlock("span", _hoisted_2, [createVNode(SidebarItemLabel_default, {
|
|
462
|
+
item: __props.item,
|
|
463
|
+
operationTitleSource: __props.options?.operationTitleSource
|
|
464
|
+
}, null, 8, ["item", "operationTitleSource"])])) : (openBlock(), createBlock(SidebarItemLabel_default, {
|
|
465
|
+
key: 1,
|
|
466
|
+
item: __props.item,
|
|
467
|
+
operationTitleSource: __props.options?.operationTitleSource
|
|
468
|
+
}, null, 8, ["item", "operationTitleSource"]))]),
|
|
469
|
+
_: 2
|
|
470
|
+
}, [
|
|
471
|
+
slots.icon ? {
|
|
472
|
+
name: "icon",
|
|
473
|
+
fn: withCtx(() => [renderSlot(_ctx.$slots, "icon", {
|
|
474
|
+
item: __props.item,
|
|
475
|
+
open: true
|
|
476
|
+
})]),
|
|
477
|
+
key: "0"
|
|
478
|
+
} : void 0,
|
|
479
|
+
"method" in __props.item ? {
|
|
480
|
+
name: "aside",
|
|
481
|
+
fn: withCtx(() => ["method" in __props.item ? (openBlock(), createBlock(SidebarHttpBadge_default, {
|
|
482
|
+
key: 0,
|
|
483
|
+
active: __props.isSelected(__props.item.id),
|
|
484
|
+
class: normalizeClass(["ml-2 h-4 self-start", { "group-hover/button:opacity-0 group-focus-visible/button:opacity-0 group-has-[~*_[aria-expanded=true]]/button:opacity-0 group-has-[~*:focus-within]/button:opacity-0 group-has-[~*:hover]/button:opacity-0": slots.decorator }]),
|
|
485
|
+
method: __props.item.method,
|
|
486
|
+
webhook: __props.item.type === "webhook"
|
|
487
|
+
}, null, 8, [
|
|
488
|
+
"active",
|
|
489
|
+
"class",
|
|
490
|
+
"method",
|
|
491
|
+
"webhook"
|
|
492
|
+
])) : createCommentVNode("", true)]),
|
|
493
|
+
key: "1"
|
|
494
|
+
} : void 0,
|
|
495
|
+
slots.decorator ? {
|
|
496
|
+
name: "after",
|
|
497
|
+
fn: withCtx(() => [createVNode(SidebarItemDecorator_default, null, {
|
|
498
|
+
default: withCtx(() => [renderSlot(_ctx.$slots, "decorator", { item: __props.item })]),
|
|
499
|
+
_: 3
|
|
500
|
+
})]),
|
|
501
|
+
key: "2"
|
|
502
|
+
} : void 0
|
|
503
|
+
]), 1040, ["data-sidebar-id", "selected"]));
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
//#endregion
|
|
508
|
+
//#region src/components/ScalarSidebar.vue
|
|
509
|
+
var ScalarSidebar_default = /* @__PURE__ */ defineComponent({
|
|
510
|
+
__name: "ScalarSidebar",
|
|
511
|
+
props: {
|
|
512
|
+
layout: {},
|
|
513
|
+
items: {},
|
|
514
|
+
isSelected: { type: Function },
|
|
515
|
+
isExpanded: { type: Function },
|
|
516
|
+
options: {},
|
|
517
|
+
indent: { default: 20 },
|
|
518
|
+
isDraggable: {},
|
|
519
|
+
isDroppable: { type: Function }
|
|
520
|
+
},
|
|
521
|
+
emits: [
|
|
522
|
+
"reorder",
|
|
523
|
+
"selectItem",
|
|
524
|
+
"toggleGroup"
|
|
525
|
+
],
|
|
526
|
+
setup(__props, { emit: __emit }) {
|
|
527
|
+
const emit = __emit;
|
|
528
|
+
const slots = useSlots();
|
|
529
|
+
/**
|
|
530
|
+
* Handle drag and drop reordering of sidebar items.
|
|
531
|
+
* Emit the reorder event to the parent component for handling.
|
|
532
|
+
*/
|
|
533
|
+
const handleDragEnd = (draggingItem, hoveredItem) => {
|
|
534
|
+
emit("reorder", draggingItem, hoveredItem);
|
|
535
|
+
};
|
|
536
|
+
return (_ctx, _cache) => {
|
|
537
|
+
return openBlock(), createBlock(unref(ScalarSidebar), {
|
|
538
|
+
class: "flex min-h-0 flex-col",
|
|
539
|
+
style: normalizeStyle({ "--scalar-sidebar-indent": __props.indent + "px" })
|
|
540
|
+
}, {
|
|
541
|
+
default: withCtx(() => [
|
|
542
|
+
renderSlot(_ctx.$slots, "header"),
|
|
543
|
+
renderSlot(_ctx.$slots, "default", {}, () => [createVNode(unref(ScalarSidebarItems), { class: "custom-scroll" }, {
|
|
544
|
+
default: withCtx(() => [renderSlot(_ctx.$slots, "before"), (openBlock(true), createElementBlock(Fragment, null, renderList(unref(filterItems)(__props.layout, __props.items), (item) => {
|
|
545
|
+
return openBlock(), createBlock(SidebarItem_default, {
|
|
546
|
+
key: item.id,
|
|
547
|
+
isDraggable: __props.isDraggable ?? __props.layout === "client",
|
|
548
|
+
isDroppable: __props.isDroppable,
|
|
549
|
+
isExpanded: __props.isExpanded,
|
|
550
|
+
isSelected: __props.isSelected,
|
|
551
|
+
item,
|
|
552
|
+
layout: __props.layout,
|
|
553
|
+
options: __props.options,
|
|
554
|
+
onOnDragEnd: handleDragEnd,
|
|
555
|
+
onSelectItem: _cache[0] || (_cache[0] = (id) => emit("selectItem", id)),
|
|
556
|
+
onToggleGroup: _cache[1] || (_cache[1] = (id) => emit("toggleGroup", id))
|
|
557
|
+
}, createSlots({ _: 2 }, [
|
|
558
|
+
slots.decorator ? {
|
|
559
|
+
name: "decorator",
|
|
560
|
+
fn: withCtx((props) => [renderSlot(_ctx.$slots, "decorator", mergeProps({ ref_for: true }, props))]),
|
|
561
|
+
key: "0"
|
|
562
|
+
} : void 0,
|
|
563
|
+
slots.empty ? {
|
|
564
|
+
name: "empty",
|
|
565
|
+
fn: withCtx((props) => [renderSlot(_ctx.$slots, "empty", mergeProps({ ref_for: true }, props))]),
|
|
566
|
+
key: "1"
|
|
567
|
+
} : void 0,
|
|
568
|
+
slots.icon ? {
|
|
569
|
+
name: "icon",
|
|
570
|
+
fn: withCtx((props) => [renderSlot(_ctx.$slots, "icon", mergeProps({ ref_for: true }, props))]),
|
|
571
|
+
key: "2"
|
|
572
|
+
} : void 0
|
|
573
|
+
]), 1032, [
|
|
574
|
+
"isDraggable",
|
|
575
|
+
"isDroppable",
|
|
576
|
+
"isExpanded",
|
|
577
|
+
"isSelected",
|
|
578
|
+
"item",
|
|
579
|
+
"layout",
|
|
580
|
+
"options"
|
|
581
|
+
]);
|
|
582
|
+
}), 128))]),
|
|
583
|
+
_: 3
|
|
584
|
+
}), renderSlot(_ctx.$slots, "spacer", {}, () => [_cache[2] || (_cache[2] = createElementVNode("div", { class: "flex-1" }, null, -1))])]),
|
|
585
|
+
renderSlot(_ctx.$slots, "footer")
|
|
586
|
+
]),
|
|
587
|
+
_: 3
|
|
588
|
+
}, 8, ["style"]);
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
//#endregion
|
|
593
|
+
//#region src/helpers/generate-reverse-index.ts
|
|
594
|
+
/**
|
|
595
|
+
* Builds a reverse index for fast ID-based lookup and attaches a `parent` property to each node for upward traversal.
|
|
596
|
+
*
|
|
597
|
+
* @template T - The node type (must include `id`).
|
|
598
|
+
* @template Key - The key pointing to nested children arrays (defaults to 'children').
|
|
599
|
+
*
|
|
600
|
+
* @param items - The root-level array of entities to index.
|
|
601
|
+
* @param nestedKey - The property name that contains child nodes within an entity (defaults to 'children').
|
|
602
|
+
* @param filter - Optional. A predicate to determine which nodes are included in the map (default: includes all).
|
|
603
|
+
* @param getId - Optional. A function to obtain the unique ID from a node (defaults to `node.id`).
|
|
604
|
+
* @returns A Map where each key is a node's ID and each value is the node, extended with an optional `parent` property.
|
|
605
|
+
*
|
|
606
|
+
* @example
|
|
607
|
+
* const data = [
|
|
608
|
+
* { id: '1', name: 'Root', children: [
|
|
609
|
+
* { id: '2', name: 'Child 1' },
|
|
610
|
+
* { id: '3', name: 'Child 2', children: [
|
|
611
|
+
* { id: '4', name: 'Grandchild' }
|
|
612
|
+
* ]}
|
|
613
|
+
* ]}
|
|
614
|
+
* ];
|
|
615
|
+
* const index = generateReverseIndex({ items: data });
|
|
616
|
+
* // index.get('3') => { id: '3', name: 'Child 2', children: [...], parent: { id: '1', ... } }
|
|
617
|
+
* // index.get('4') => { id: '4', name: 'Grandchild', parent: { id: '3', ... } }
|
|
618
|
+
*/
|
|
619
|
+
var generateReverseIndex = ({ items, nestedKey = "children", filter = () => true, getId = (node) => node.id }) => {
|
|
620
|
+
const mapping = /* @__PURE__ */ new Map();
|
|
621
|
+
const dfs = (node) => {
|
|
622
|
+
if (filter(node)) mapping.set(getId(node), node);
|
|
623
|
+
if (nestedKey in node && Array.isArray(node[nestedKey])) node[nestedKey]?.forEach((it) => dfs({
|
|
624
|
+
...it,
|
|
625
|
+
parent: node
|
|
626
|
+
}));
|
|
627
|
+
};
|
|
628
|
+
items.forEach(dfs);
|
|
629
|
+
return mapping;
|
|
630
|
+
};
|
|
631
|
+
//#endregion
|
|
632
|
+
//#region src/helpers/create-sidebar-state.ts
|
|
633
|
+
/**
|
|
634
|
+
* Creates and manages the state for a sidebar tree, including selection and expansion of items.
|
|
635
|
+
*
|
|
636
|
+
* @template T - The type of sidebar items, which must include an `id` property.
|
|
637
|
+
* @param items - The array of sidebar items.
|
|
638
|
+
* @param options - Optional configuration for customizing behavior and hooks.
|
|
639
|
+
* @returns An object containing sidebar state and methods to manipulate it.
|
|
640
|
+
*
|
|
641
|
+
* ## Example Usage
|
|
642
|
+
*
|
|
643
|
+
* ```ts
|
|
644
|
+
* // Example sidebar items
|
|
645
|
+
* const sidebarItems = [
|
|
646
|
+
* { id: 'root', label: 'Root', children: [
|
|
647
|
+
* { id: 'child1', label: 'Child 1' },
|
|
648
|
+
* { id: 'child2', label: 'Child 2', children: [
|
|
649
|
+
* { id: 'grandchild1', label: 'Grandchild 1' }
|
|
650
|
+
* ]}
|
|
651
|
+
* ]}
|
|
652
|
+
* ]
|
|
653
|
+
*
|
|
654
|
+
* // Create sidebar state
|
|
655
|
+
* const sidebarState = createSidebarState(sidebarItems, {
|
|
656
|
+
* key: 'children',
|
|
657
|
+
* hooks: {
|
|
658
|
+
* onBeforeSelect: (id) => console.log('Before select:', id),
|
|
659
|
+
* onAfterSelect: (id) => console.log('After select:', id),
|
|
660
|
+
* onBeforeExpand: (id) => console.log('Before expand:', id),
|
|
661
|
+
* onAfterExpand: (id) => console.log('After expand:', id),
|
|
662
|
+
* }
|
|
663
|
+
* })
|
|
664
|
+
*
|
|
665
|
+
* // Select an item
|
|
666
|
+
* await sidebarState.setSelected('grandchild1')
|
|
667
|
+
* // Expand an item
|
|
668
|
+
* await sidebarState.setExpanded('child2', true)
|
|
669
|
+
* ```
|
|
670
|
+
*/
|
|
671
|
+
var createSidebarState = (items, options) => {
|
|
672
|
+
const index = computed(() => generateReverseIndex({
|
|
673
|
+
items: toValue(items),
|
|
674
|
+
nestedKey: options?.key ?? "children"
|
|
675
|
+
}));
|
|
676
|
+
const selectedItems = ref({});
|
|
677
|
+
const expandedItems = ref({});
|
|
678
|
+
const selectedItem = ref(null);
|
|
679
|
+
/**
|
|
680
|
+
* Selects the given item by id, and recursively marks all its parent items as selected.
|
|
681
|
+
* Triggers optional lifecycle hooks before and after selection.
|
|
682
|
+
*
|
|
683
|
+
* @param id - The ID of the item to select.
|
|
684
|
+
*
|
|
685
|
+
* ## Example
|
|
686
|
+
* ```ts
|
|
687
|
+
* await sidebarState.setSelected('grandchild1')
|
|
688
|
+
* // selectedItems.value will include 'grandchild1', 'child2', and 'root'
|
|
689
|
+
* ```
|
|
690
|
+
*/
|
|
691
|
+
const setSelected = (id) => {
|
|
692
|
+
/**
|
|
693
|
+
* Recursively mark all parent items as selected.
|
|
694
|
+
* @param node - The current node to mark as selected.
|
|
695
|
+
*/
|
|
696
|
+
const markSelected = (node) => {
|
|
697
|
+
if (!node) return;
|
|
698
|
+
selectedItems.value[node.id] = true;
|
|
699
|
+
if ("parent" in node && node.parent) markSelected(node.parent);
|
|
700
|
+
};
|
|
701
|
+
if (options?.hooks?.onBeforeSelect) options.hooks.onBeforeSelect(id);
|
|
702
|
+
selectedItems.value = {};
|
|
703
|
+
selectedItem.value = id;
|
|
704
|
+
if (id !== null) markSelected(index.value.get(id));
|
|
705
|
+
if (options?.hooks?.onAfterSelect) options.hooks.onAfterSelect(id);
|
|
706
|
+
};
|
|
707
|
+
/**
|
|
708
|
+
* Expands or collapses the given item by id.
|
|
709
|
+
* When expanding, recursively expands all parent items.
|
|
710
|
+
* Triggers optional lifecycle hooks before and after expansion.
|
|
711
|
+
*
|
|
712
|
+
* @param id - The ID of the item to expand or collapse.
|
|
713
|
+
* @param value - true to expand, false to collapse.
|
|
714
|
+
*
|
|
715
|
+
* ## Example
|
|
716
|
+
* ```ts
|
|
717
|
+
* await sidebarState.setExpanded('child2', true)
|
|
718
|
+
* // expandedItems.value will include 'child2' and 'root'
|
|
719
|
+
*
|
|
720
|
+
* await sidebarState.setExpanded('child2', false)
|
|
721
|
+
* // expandedItems.value['child2'] will be false
|
|
722
|
+
* ```
|
|
723
|
+
*/
|
|
724
|
+
const setExpanded = (id, value) => {
|
|
725
|
+
/**
|
|
726
|
+
* Recursively expand all parent items of the given node.
|
|
727
|
+
* @param node - The current node to expand.
|
|
728
|
+
*/
|
|
729
|
+
const openParents = (node) => {
|
|
730
|
+
if (!node) return;
|
|
731
|
+
expandedItems.value[node.id] = true;
|
|
732
|
+
if ("parent" in node && node.parent) openParents(node.parent);
|
|
733
|
+
};
|
|
734
|
+
if (options?.hooks?.onBeforeExpand) options.hooks.onBeforeExpand(id);
|
|
735
|
+
if (value === false) expandedItems.value[id] = false;
|
|
736
|
+
else openParents(index.value.get(id));
|
|
737
|
+
if (options?.hooks?.onAfterExpand) options.hooks.onAfterExpand(id);
|
|
738
|
+
};
|
|
739
|
+
const isExpanded = (id) => {
|
|
740
|
+
return expandedItems.value[id] ?? false;
|
|
741
|
+
};
|
|
742
|
+
const isSelected = (id) => {
|
|
743
|
+
return selectedItems.value[id] ?? false;
|
|
744
|
+
};
|
|
745
|
+
const getEntryById = (id) => index.value.get(id);
|
|
746
|
+
return {
|
|
747
|
+
items: computed(() => toValue(items)),
|
|
748
|
+
index,
|
|
749
|
+
selectedItems,
|
|
750
|
+
expandedItems,
|
|
751
|
+
selectedItem,
|
|
752
|
+
setSelected,
|
|
753
|
+
setExpanded,
|
|
754
|
+
isExpanded,
|
|
755
|
+
isSelected,
|
|
756
|
+
getEntryById,
|
|
757
|
+
reset: () => {
|
|
758
|
+
selectedItems.value = {};
|
|
759
|
+
expandedItems.value = {};
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
};
|
|
763
|
+
//#endregion
|
|
764
|
+
//#region src/helpers/get-child-entry.ts
|
|
765
|
+
/**
|
|
766
|
+
* Recursively searches for and returns the first child node (including the given node itself)
|
|
767
|
+
* of a specific type within the provided node's subtree.
|
|
768
|
+
*
|
|
769
|
+
* @template Type - The type of node to search for.
|
|
770
|
+
* @param type - The node type to match.
|
|
771
|
+
* @param node - The root node to begin searching from.
|
|
772
|
+
* @returns The first child node of the specified type, or null if not found.
|
|
773
|
+
*/
|
|
774
|
+
var getChildEntry = (type, node) => {
|
|
775
|
+
if (node.type === type) return node;
|
|
776
|
+
if ("children" in node) for (const child of node.children ?? []) {
|
|
777
|
+
const result = getChildEntry(type, child);
|
|
778
|
+
if (result) return result;
|
|
779
|
+
}
|
|
780
|
+
return null;
|
|
781
|
+
};
|
|
782
|
+
//#endregion
|
|
783
|
+
//#region src/helpers/scroll-sidebar-to-top.ts
|
|
784
|
+
var DEFAULT_SCROLL_OFFSET_TOP = 100;
|
|
785
|
+
/**
|
|
786
|
+
* Returns the element that should be used for vertical position measurement.
|
|
787
|
+
*/
|
|
788
|
+
var getMeasurableElement = (element) => {
|
|
789
|
+
if (window.getComputedStyle(element).display !== "contents") return element;
|
|
790
|
+
/**
|
|
791
|
+
* `display: contents` does not render a layout box, so use the first child with
|
|
792
|
+
* a measurable offset to keep the scroll target aligned with what users see.
|
|
793
|
+
*/
|
|
794
|
+
for (const child of element.children) if (child instanceof HTMLElement && child.offsetParent !== null) return child;
|
|
795
|
+
return element;
|
|
796
|
+
};
|
|
797
|
+
/**
|
|
798
|
+
* Adds extra offset for heading rows so the selected item stays visible below labels.
|
|
799
|
+
*/
|
|
800
|
+
var getHeadingOffset = (element) => {
|
|
801
|
+
if (element.dataset.sidebarType !== "heading") return 0;
|
|
802
|
+
return element.querySelector(".sidebar-heading")?.offsetHeight ?? 0;
|
|
803
|
+
};
|
|
804
|
+
/**
|
|
805
|
+
* Computes an element top position relative to the provided scroll container.
|
|
806
|
+
*/
|
|
807
|
+
var getTopRelativeToScroller = (element, scroller) => {
|
|
808
|
+
let top = element.offsetTop;
|
|
809
|
+
let currentOffsetParent = element.offsetParent;
|
|
810
|
+
while (currentOffsetParent && currentOffsetParent !== scroller) {
|
|
811
|
+
top += currentOffsetParent.offsetTop;
|
|
812
|
+
currentOffsetParent = currentOffsetParent.offsetParent;
|
|
813
|
+
}
|
|
814
|
+
return top;
|
|
815
|
+
};
|
|
816
|
+
/**
|
|
817
|
+
* Scrolls the sidebar container so the requested item appears near the top.
|
|
818
|
+
*/
|
|
819
|
+
var scrollSidebarToTop = (id, offsetTop = DEFAULT_SCROLL_OFFSET_TOP) => {
|
|
820
|
+
if (typeof window === "undefined") return;
|
|
821
|
+
const element = document.querySelector(`[data-sidebar-id="${id}"]`);
|
|
822
|
+
const scroller = element?.closest(".custom-scroll, .custom-scrollbar") ?? null;
|
|
823
|
+
if (!element || !scroller) return;
|
|
824
|
+
const targetTop = getTopRelativeToScroller(getMeasurableElement(element), scroller) + getHeadingOffset(element) - offsetTop;
|
|
825
|
+
scroller.scrollTo({
|
|
826
|
+
top: targetTop > 0 ? targetTop : 0,
|
|
827
|
+
behavior: "smooth"
|
|
828
|
+
});
|
|
829
|
+
};
|
|
830
|
+
//#endregion
|
|
831
|
+
export { HttpMethod_default as HttpMethod, ScalarSidebar_default as ScalarSidebar, SidebarHttpBadge_default as SidebarHttpBadge, SidebarItem_default as SidebarItem, createSidebarState, generateReverseIndex, getChildEntry, scrollSidebarToTop, useDraggable };
|
|
832
|
+
|
|
833
|
+
//# sourceMappingURL=index.js.map
|