@scalar/sidebar 0.8.11 → 0.8.13
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/_virtual/_plugin-vue_export-helper.js +8 -0
- package/dist/components/HttpMethod.vue.js +7 -0
- package/dist/components/HttpMethod.vue.js.map +1 -0
- package/dist/components/HttpMethod.vue.script.js +33 -0
- package/dist/components/HttpMethod.vue.script.js.map +1 -0
- package/dist/components/ScalarSidebar.vue.d.ts +2 -2
- package/dist/components/ScalarSidebar.vue.js +7 -0
- package/dist/components/ScalarSidebar.vue.js.map +1 -0
- package/dist/components/ScalarSidebar.vue.script.js +92 -0
- package/dist/components/ScalarSidebar.vue.script.js.map +1 -0
- package/dist/components/SidebarHttpBadge.vue.js +9 -0
- package/dist/components/SidebarHttpBadge.vue.js.map +1 -0
- package/dist/components/SidebarHttpBadge.vue.script.js +38 -0
- package/dist/components/SidebarHttpBadge.vue.script.js.map +1 -0
- package/dist/components/SidebarItem.vue.d.ts +2 -2
- package/dist/components/SidebarItem.vue.js +7 -0
- package/dist/components/SidebarItem.vue.js.map +1 -0
- package/dist/components/SidebarItem.vue.script.js +266 -0
- package/dist/components/SidebarItem.vue.script.js.map +1 -0
- package/dist/components/SidebarItemDecorator.vue.js +13 -0
- package/dist/components/SidebarItemDecorator.vue.js.map +1 -0
- package/dist/components/SidebarItemLabel.vue.d.ts +1 -1
- package/dist/components/SidebarItemLabel.vue.js +7 -0
- package/dist/components/SidebarItemLabel.vue.js.map +1 -0
- package/dist/components/SidebarItemLabel.vue.script.js +26 -0
- package/dist/components/SidebarItemLabel.vue.script.js.map +1 -0
- package/dist/helpers/create-sidebar-state.js +137 -0
- package/dist/helpers/create-sidebar-state.js.map +1 -0
- package/dist/helpers/filter-items.d.ts +1 -1
- package/dist/helpers/filter-items.js +16 -0
- package/dist/helpers/filter-items.js.map +1 -0
- package/dist/helpers/generate-reverse-index.js +42 -0
- package/dist/helpers/generate-reverse-index.js.map +1 -0
- package/dist/helpers/get-child-entry.js +22 -0
- package/dist/helpers/get-child-entry.js.map +1 -0
- package/dist/helpers/has-children.d.ts +1 -1
- package/dist/helpers/has-children.js +12 -0
- package/dist/helpers/has-children.js.map +1 -0
- package/dist/helpers/is-sidebar-folder.d.ts +1 -1
- package/dist/helpers/is-sidebar-folder.js +25 -0
- package/dist/helpers/is-sidebar-folder.js.map +1 -0
- package/dist/helpers/scroll-sidebar-to-top.js +51 -0
- package/dist/helpers/scroll-sidebar-to-top.js.map +1 -0
- package/dist/hooks/use-draggable.js +112 -0
- package/dist/hooks/use-draggable.js.map +1 -0
- package/dist/index.d.ts +9 -9
- package/dist/index.js +10 -832
- package/dist/style.css +2306 -2314
- package/package.json +7 -7
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import _plugin_vue_export_helper_default from "../_virtual/_plugin-vue_export-helper.js";
|
|
2
|
+
import { createElementBlock, openBlock, renderSlot } from "vue";
|
|
3
|
+
//#region src/components/SidebarItemDecorator.vue
|
|
4
|
+
var _sfc_main = {};
|
|
5
|
+
var _hoisted_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" };
|
|
6
|
+
function _sfc_render(_ctx, _cache) {
|
|
7
|
+
return openBlock(), createElementBlock("div", _hoisted_1, [renderSlot(_ctx.$slots, "default")]);
|
|
8
|
+
}
|
|
9
|
+
var SidebarItemDecorator_default = /* @__PURE__ */ _plugin_vue_export_helper_default(_sfc_main, [["render", _sfc_render]]);
|
|
10
|
+
//#endregion
|
|
11
|
+
export { SidebarItemDecorator_default as default };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=SidebarItemDecorator.vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SidebarItemDecorator.vue.js","names":[],"sources":["../../src/components/SidebarItemDecorator.vue"],"sourcesContent":["<template>\n <div\n 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\">\n <slot />\n </div>\n</template>\n"],"mappings":";;;;mBAEI,OAAM,qPAAmP;;qBAD3P,mBAGM,OAHN,YAGM,CADJ,WAAQ,KAAA,QAAA,UAAA,CAAA,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import SidebarItemLabel_vue_vue_type_script_setup_true_lang_default from "./SidebarItemLabel.vue.script.js";
|
|
2
|
+
//#region src/components/SidebarItemLabel.vue
|
|
3
|
+
var SidebarItemLabel_default = SidebarItemLabel_vue_vue_type_script_setup_true_lang_default;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { SidebarItemLabel_default as default };
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=SidebarItemLabel.vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SidebarItemLabel.vue.js","names":[],"sources":["../../src/components/SidebarItemLabel.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport { ScalarWrappingText } from '@scalar/components'\n\nimport type { Item } from '@/types'\n\nconst { item } = defineProps<{\n /**\n * The sidebar item to render.\n */\n item: Item\n /**\n * The source of the operation title.\n */\n operationTitleSource?: 'path' | 'summary'\n}>()\n</script>\n<template>\n <template v-if=\"item.type === 'model'\">\n <ScalarWrappingText\n preset=\"property\"\n :text=\"item.title\" />\n </template>\n <template v-else>\n <ScalarWrappingText\n :text=\"\n operationTitleSource === 'path' && 'path' in item\n ? (item.path as string)\n : (item.title as string)\n \" />\n </template>\n</template>\n"],"mappings":""}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createBlock, defineComponent, openBlock, unref } from "vue";
|
|
2
|
+
import { ScalarWrappingText } from "@scalar/components";
|
|
3
|
+
//#region src/components/SidebarItemLabel.vue?vue&type=script&setup=true&lang.ts
|
|
4
|
+
var SidebarItemLabel_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
|
|
5
|
+
__name: "SidebarItemLabel",
|
|
6
|
+
props: {
|
|
7
|
+
item: {},
|
|
8
|
+
operationTitleSource: {}
|
|
9
|
+
},
|
|
10
|
+
setup(__props) {
|
|
11
|
+
return (_ctx, _cache) => {
|
|
12
|
+
return __props.item.type === "model" ? (openBlock(), createBlock(unref(ScalarWrappingText), {
|
|
13
|
+
key: 0,
|
|
14
|
+
preset: "property",
|
|
15
|
+
text: __props.item.title
|
|
16
|
+
}, null, 8, ["text"])) : (openBlock(), createBlock(unref(ScalarWrappingText), {
|
|
17
|
+
key: 1,
|
|
18
|
+
text: __props.operationTitleSource === "path" && "path" in __props.item ? __props.item.path : __props.item.title
|
|
19
|
+
}, null, 8, ["text"]));
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
//#endregion
|
|
24
|
+
export { SidebarItemLabel_vue_vue_type_script_setup_true_lang_default as default };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=SidebarItemLabel.vue.script.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SidebarItemLabel.vue.script.js","names":[],"sources":["../../src/components/SidebarItemLabel.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport { ScalarWrappingText } from '@scalar/components'\n\nimport type { Item } from '@/types'\n\nconst { item } = defineProps<{\n /**\n * The sidebar item to render.\n */\n item: Item\n /**\n * The source of the operation title.\n */\n operationTitleSource?: 'path' | 'summary'\n}>()\n</script>\n<template>\n <template v-if=\"item.type === 'model'\">\n <ScalarWrappingText\n preset=\"property\"\n :text=\"item.title\" />\n </template>\n <template v-else>\n <ScalarWrappingText\n :text=\"\n operationTitleSource === 'path' && 'path' in item\n ? (item.path as string)\n : (item.title as string)\n \" />\n </template>\n</template>\n"],"mappings":";;;;;;;;;;;UAiBkB,QAAA,KAAK,SAAI,WAAA,WAAA,EACvB,YAEuB,MAAA,mBAAA,EAAA;;IADrB,QAAO;IACN,MAAM,QAAA,KAAK;0CAGd,YAKM,MAAA,mBAAA,EAAA;;IAJH,MAAe,QAAA,yBAAoB,UAAA,UAAyB,QAAA,OAAkB,QAAA,KAAK,OAA6B,QAAA,KAAK"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { generateReverseIndex } from "./generate-reverse-index.js";
|
|
2
|
+
import { computed, ref, toValue } from "vue";
|
|
3
|
+
//#region src/helpers/create-sidebar-state.ts
|
|
4
|
+
/**
|
|
5
|
+
* Creates and manages the state for a sidebar tree, including selection and expansion of items.
|
|
6
|
+
*
|
|
7
|
+
* @template T - The type of sidebar items, which must include an `id` property.
|
|
8
|
+
* @param items - The array of sidebar items.
|
|
9
|
+
* @param options - Optional configuration for customizing behavior and hooks.
|
|
10
|
+
* @returns An object containing sidebar state and methods to manipulate it.
|
|
11
|
+
*
|
|
12
|
+
* ## Example Usage
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* // Example sidebar items
|
|
16
|
+
* const sidebarItems = [
|
|
17
|
+
* { id: 'root', label: 'Root', children: [
|
|
18
|
+
* { id: 'child1', label: 'Child 1' },
|
|
19
|
+
* { id: 'child2', label: 'Child 2', children: [
|
|
20
|
+
* { id: 'grandchild1', label: 'Grandchild 1' }
|
|
21
|
+
* ]}
|
|
22
|
+
* ]}
|
|
23
|
+
* ]
|
|
24
|
+
*
|
|
25
|
+
* // Create sidebar state
|
|
26
|
+
* const sidebarState = createSidebarState(sidebarItems, {
|
|
27
|
+
* key: 'children',
|
|
28
|
+
* hooks: {
|
|
29
|
+
* onBeforeSelect: (id) => console.log('Before select:', id),
|
|
30
|
+
* onAfterSelect: (id) => console.log('After select:', id),
|
|
31
|
+
* onBeforeExpand: (id) => console.log('Before expand:', id),
|
|
32
|
+
* onAfterExpand: (id) => console.log('After expand:', id),
|
|
33
|
+
* }
|
|
34
|
+
* })
|
|
35
|
+
*
|
|
36
|
+
* // Select an item
|
|
37
|
+
* await sidebarState.setSelected('grandchild1')
|
|
38
|
+
* // Expand an item
|
|
39
|
+
* await sidebarState.setExpanded('child2', true)
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
var createSidebarState = (items, options) => {
|
|
43
|
+
const index = computed(() => generateReverseIndex({
|
|
44
|
+
items: toValue(items),
|
|
45
|
+
nestedKey: options?.key ?? "children"
|
|
46
|
+
}));
|
|
47
|
+
const selectedItems = ref({});
|
|
48
|
+
const expandedItems = ref({});
|
|
49
|
+
const selectedItem = ref(null);
|
|
50
|
+
/**
|
|
51
|
+
* Selects the given item by id, and recursively marks all its parent items as selected.
|
|
52
|
+
* Triggers optional lifecycle hooks before and after selection.
|
|
53
|
+
*
|
|
54
|
+
* @param id - The ID of the item to select.
|
|
55
|
+
*
|
|
56
|
+
* ## Example
|
|
57
|
+
* ```ts
|
|
58
|
+
* await sidebarState.setSelected('grandchild1')
|
|
59
|
+
* // selectedItems.value will include 'grandchild1', 'child2', and 'root'
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
const setSelected = (id) => {
|
|
63
|
+
/**
|
|
64
|
+
* Recursively mark all parent items as selected.
|
|
65
|
+
* @param node - The current node to mark as selected.
|
|
66
|
+
*/
|
|
67
|
+
const markSelected = (node) => {
|
|
68
|
+
if (!node) return;
|
|
69
|
+
selectedItems.value[node.id] = true;
|
|
70
|
+
if ("parent" in node && node.parent) markSelected(node.parent);
|
|
71
|
+
};
|
|
72
|
+
if (options?.hooks?.onBeforeSelect) options.hooks.onBeforeSelect(id);
|
|
73
|
+
selectedItems.value = {};
|
|
74
|
+
selectedItem.value = id;
|
|
75
|
+
if (id !== null) markSelected(index.value.get(id));
|
|
76
|
+
if (options?.hooks?.onAfterSelect) options.hooks.onAfterSelect(id);
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Expands or collapses the given item by id.
|
|
80
|
+
* When expanding, recursively expands all parent items.
|
|
81
|
+
* Triggers optional lifecycle hooks before and after expansion.
|
|
82
|
+
*
|
|
83
|
+
* @param id - The ID of the item to expand or collapse.
|
|
84
|
+
* @param value - true to expand, false to collapse.
|
|
85
|
+
*
|
|
86
|
+
* ## Example
|
|
87
|
+
* ```ts
|
|
88
|
+
* await sidebarState.setExpanded('child2', true)
|
|
89
|
+
* // expandedItems.value will include 'child2' and 'root'
|
|
90
|
+
*
|
|
91
|
+
* await sidebarState.setExpanded('child2', false)
|
|
92
|
+
* // expandedItems.value['child2'] will be false
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
const setExpanded = (id, value) => {
|
|
96
|
+
/**
|
|
97
|
+
* Recursively expand all parent items of the given node.
|
|
98
|
+
* @param node - The current node to expand.
|
|
99
|
+
*/
|
|
100
|
+
const openParents = (node) => {
|
|
101
|
+
if (!node) return;
|
|
102
|
+
expandedItems.value[node.id] = true;
|
|
103
|
+
if ("parent" in node && node.parent) openParents(node.parent);
|
|
104
|
+
};
|
|
105
|
+
if (options?.hooks?.onBeforeExpand) options.hooks.onBeforeExpand(id);
|
|
106
|
+
if (value === false) expandedItems.value[id] = false;
|
|
107
|
+
else openParents(index.value.get(id));
|
|
108
|
+
if (options?.hooks?.onAfterExpand) options.hooks.onAfterExpand(id);
|
|
109
|
+
};
|
|
110
|
+
const isExpanded = (id) => {
|
|
111
|
+
return expandedItems.value[id] ?? false;
|
|
112
|
+
};
|
|
113
|
+
const isSelected = (id) => {
|
|
114
|
+
return selectedItems.value[id] ?? false;
|
|
115
|
+
};
|
|
116
|
+
const getEntryById = (id) => index.value.get(id);
|
|
117
|
+
return {
|
|
118
|
+
items: computed(() => toValue(items)),
|
|
119
|
+
index,
|
|
120
|
+
selectedItems,
|
|
121
|
+
expandedItems,
|
|
122
|
+
selectedItem,
|
|
123
|
+
setSelected,
|
|
124
|
+
setExpanded,
|
|
125
|
+
isExpanded,
|
|
126
|
+
isSelected,
|
|
127
|
+
getEntryById,
|
|
128
|
+
reset: () => {
|
|
129
|
+
selectedItems.value = {};
|
|
130
|
+
expandedItems.value = {};
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
//#endregion
|
|
135
|
+
export { createSidebarState };
|
|
136
|
+
|
|
137
|
+
//# sourceMappingURL=create-sidebar-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-sidebar-state.js","names":[],"sources":["../../src/helpers/create-sidebar-state.ts"],"sourcesContent":["import { type MaybeRefOrGetter, computed, ref, toValue } from 'vue'\n\nimport { generateReverseIndex } from './generate-reverse-index'\n\ntype SidebarStateOptions = Partial<{\n /** The key used to access child items in the sidebar tree. Defaults to \"children\". */\n key: string\n /** Optional event hooks for sidebar item lifecycle */\n hooks: Partial<{\n /**\n * Called before an item is expanded.\n * @param id - The ID of the item to expand.\n */\n onBeforeExpand: (id: string) => void\n /**\n * Called after an item is expanded.\n * @param id - The ID of the item that was expanded.\n */\n onAfterExpand: (id: string) => void\n /**\n * Called just before a sidebar item selection changes.\n * @param id - The ID of the item that is about to be selected, or null if deselecting.\n */\n onBeforeSelect: (id: string | null) => void\n /**\n * Called immediately after a sidebar item has been selected.\n * @param id - The ID of the item that was selected, or null if selection was cleared.\n */\n onAfterSelect: (id: string | null) => void\n }>\n}>\n\n/**\n * Creates and manages the state for a sidebar tree, including selection and expansion of items.\n *\n * @template T - The type of sidebar items, which must include an `id` property.\n * @param items - The array of sidebar items.\n * @param options - Optional configuration for customizing behavior and hooks.\n * @returns An object containing sidebar state and methods to manipulate it.\n *\n * ## Example Usage\n *\n * ```ts\n * // Example sidebar items\n * const sidebarItems = [\n * { id: 'root', label: 'Root', children: [\n * { id: 'child1', label: 'Child 1' },\n * { id: 'child2', label: 'Child 2', children: [\n * { id: 'grandchild1', label: 'Grandchild 1' }\n * ]}\n * ]}\n * ]\n *\n * // Create sidebar state\n * const sidebarState = createSidebarState(sidebarItems, {\n * key: 'children',\n * hooks: {\n * onBeforeSelect: (id) => console.log('Before select:', id),\n * onAfterSelect: (id) => console.log('After select:', id),\n * onBeforeExpand: (id) => console.log('Before expand:', id),\n * onAfterExpand: (id) => console.log('After expand:', id),\n * }\n * })\n *\n * // Select an item\n * await sidebarState.setSelected('grandchild1')\n * // Expand an item\n * await sidebarState.setExpanded('child2', true)\n * ```\n */\nexport const createSidebarState = <T extends { id: string }>(\n items: MaybeRefOrGetter<T[]>,\n options?: SidebarStateOptions,\n) => {\n // Reverse index for quick lookup of items and their parents\n const index = computed(() =>\n generateReverseIndex({\n items: toValue(items),\n nestedKey: options?.key ?? 'children',\n }),\n )\n // Reactive record of selected item ids\n const selectedItems = ref<Record<string, boolean>>({})\n // Reactive record of expanded item ids\n const expandedItems = ref<Record<string, boolean>>({})\n\n // Lowest level selected item\n const selectedItem = ref<string | null>(null)\n\n /**\n * Selects the given item by id, and recursively marks all its parent items as selected.\n * Triggers optional lifecycle hooks before and after selection.\n *\n * @param id - The ID of the item to select.\n *\n * ## Example\n * ```ts\n * await sidebarState.setSelected('grandchild1')\n * // selectedItems.value will include 'grandchild1', 'child2', and 'root'\n * ```\n */\n const setSelected = (id: string | null) => {\n /**\n * Recursively mark all parent items as selected.\n * @param node - The current node to mark as selected.\n */\n const markSelected = (node: (T & { parent?: T }) | undefined) => {\n if (!node) {\n return\n }\n selectedItems.value[node.id] = true\n if ('parent' in node && node.parent) {\n markSelected(node.parent)\n }\n }\n\n // Call onBeforeSelect hook if provided\n if (options?.hooks?.onBeforeSelect) {\n options.hooks.onBeforeSelect(id)\n }\n\n // Clear previous selection\n selectedItems.value = {}\n selectedItem.value = id\n\n // If id is null, do not select anything\n // We already cleared the selection above\n if (id !== null) {\n // Mark the selected item and all its parents as selected\n markSelected(index.value.get(id))\n }\n\n // Call onAfterSelect hook if provided\n if (options?.hooks?.onAfterSelect) {\n options.hooks.onAfterSelect(id)\n }\n }\n\n /**\n * Expands or collapses the given item by id.\n * When expanding, recursively expands all parent items.\n * Triggers optional lifecycle hooks before and after expansion.\n *\n * @param id - The ID of the item to expand or collapse.\n * @param value - true to expand, false to collapse.\n *\n * ## Example\n * ```ts\n * await sidebarState.setExpanded('child2', true)\n * // expandedItems.value will include 'child2' and 'root'\n *\n * await sidebarState.setExpanded('child2', false)\n * // expandedItems.value['child2'] will be false\n * ```\n */\n const setExpanded = (id: string, value: boolean) => {\n /**\n * Recursively expand all parent items of the given node.\n * @param node - The current node to expand.\n */\n const openParents = (node: (T & { parent?: T }) | undefined) => {\n if (!node) {\n return\n }\n expandedItems.value[node.id] = true\n if ('parent' in node && node.parent) {\n openParents(node.parent)\n }\n }\n\n // Call onBeforeExpand hook if provided\n if (options?.hooks?.onBeforeExpand) {\n options.hooks.onBeforeExpand(id)\n }\n\n // When collapsing, only collapse the specified item, not its parents\n if (value === false) {\n expandedItems.value[id] = false\n } else {\n // When expanding, ensure all parents are expanded as well\n openParents(index.value.get(id))\n }\n\n // Call onAfterExpand hook if provided\n if (options?.hooks?.onAfterExpand) {\n options.hooks.onAfterExpand(id)\n }\n }\n\n const isExpanded = (id: string) => {\n return expandedItems.value[id] ?? false\n }\n\n const isSelected = (id: string) => {\n return selectedItems.value[id] ?? false\n }\n\n const getEntryById = (id: string) => index.value.get(id)\n\n return {\n items: computed(() => toValue(items)),\n index,\n selectedItems,\n expandedItems,\n selectedItem,\n setSelected,\n setExpanded,\n isExpanded,\n isSelected,\n getEntryById,\n reset: () => {\n selectedItems.value = {}\n expandedItems.value = {}\n },\n }\n}\n\nexport type SidebarState<Item extends { id: string }> = ReturnType<typeof createSidebarState<Item>>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsEA,IAAa,sBACX,OACA,YACG;CAEH,MAAM,QAAQ,eACZ,qBAAqB;EACnB,OAAO,QAAQ,MAAM;EACrB,WAAW,SAAS,OAAO;EAC5B,CAAC,CACH;CAED,MAAM,gBAAgB,IAA6B,EAAE,CAAC;CAEtD,MAAM,gBAAgB,IAA6B,EAAE,CAAC;CAGtD,MAAM,eAAe,IAAmB,KAAK;;;;;;;;;;;;;CAc7C,MAAM,eAAe,OAAsB;;;;;EAKzC,MAAM,gBAAgB,SAA2C;AAC/D,OAAI,CAAC,KACH;AAEF,iBAAc,MAAM,KAAK,MAAM;AAC/B,OAAI,YAAY,QAAQ,KAAK,OAC3B,cAAa,KAAK,OAAO;;AAK7B,MAAI,SAAS,OAAO,eAClB,SAAQ,MAAM,eAAe,GAAG;AAIlC,gBAAc,QAAQ,EAAE;AACxB,eAAa,QAAQ;AAIrB,MAAI,OAAO,KAET,cAAa,MAAM,MAAM,IAAI,GAAG,CAAC;AAInC,MAAI,SAAS,OAAO,cAClB,SAAQ,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;CAqBnC,MAAM,eAAe,IAAY,UAAmB;;;;;EAKlD,MAAM,eAAe,SAA2C;AAC9D,OAAI,CAAC,KACH;AAEF,iBAAc,MAAM,KAAK,MAAM;AAC/B,OAAI,YAAY,QAAQ,KAAK,OAC3B,aAAY,KAAK,OAAO;;AAK5B,MAAI,SAAS,OAAO,eAClB,SAAQ,MAAM,eAAe,GAAG;AAIlC,MAAI,UAAU,MACZ,eAAc,MAAM,MAAM;MAG1B,aAAY,MAAM,MAAM,IAAI,GAAG,CAAC;AAIlC,MAAI,SAAS,OAAO,cAClB,SAAQ,MAAM,cAAc,GAAG;;CAInC,MAAM,cAAc,OAAe;AACjC,SAAO,cAAc,MAAM,OAAO;;CAGpC,MAAM,cAAc,OAAe;AACjC,SAAO,cAAc,MAAM,OAAO;;CAGpC,MAAM,gBAAgB,OAAe,MAAM,MAAM,IAAI,GAAG;AAExD,QAAO;EACL,OAAO,eAAe,QAAQ,MAAM,CAAC;EACrC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aAAa;AACX,iBAAc,QAAQ,EAAE;AACxB,iBAAc,QAAQ,EAAE;;EAE3B"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { Item, Layout } from '
|
|
1
|
+
import type { Item, Layout } from '../types';
|
|
2
2
|
export declare const filterItems: (layout: Layout, items: Item[]) => import("@scalar/workspace-store/schemas/navigation").TraversedEntry[];
|
|
3
3
|
//# sourceMappingURL=filter-items.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/helpers/filter-items.ts
|
|
2
|
+
/** The type of items we want to */
|
|
3
|
+
var API_CLIENT_TYPES_SET = new Set([
|
|
4
|
+
"document",
|
|
5
|
+
"operation",
|
|
6
|
+
"example",
|
|
7
|
+
"tag"
|
|
8
|
+
]);
|
|
9
|
+
var filterItems = (layout, items) => {
|
|
10
|
+
if (layout === "reference") return items;
|
|
11
|
+
return items.filter((c) => API_CLIENT_TYPES_SET.has(c.type));
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
export { filterItems };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=filter-items.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-items.js","names":[],"sources":["../../src/helpers/filter-items.ts"],"sourcesContent":["import type { Item, Layout } from '@/types'\n\n/** The type of items we want to */\nconst API_CLIENT_TYPES_SET = new Set(['document', 'operation', 'example', 'tag'])\n\nexport const filterItems = (layout: Layout, items: Item[]) => {\n if (layout === 'reference') {\n return items\n }\n\n // For client layout, filter to only show documents, operations, examples, and tags\n return items.filter((c) => API_CLIENT_TYPES_SET.has(c.type))\n}\n"],"mappings":";;AAGA,IAAM,uBAAuB,IAAI,IAAI;CAAC;CAAY;CAAa;CAAW;CAAM,CAAC;AAEjF,IAAa,eAAe,QAAgB,UAAkB;AAC5D,KAAI,WAAW,YACb,QAAO;AAIT,QAAO,MAAM,QAAQ,MAAM,qBAAqB,IAAI,EAAE,KAAK,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//#region src/helpers/generate-reverse-index.ts
|
|
2
|
+
/**
|
|
3
|
+
* Builds a reverse index for fast ID-based lookup and attaches a `parent` property to each node for upward traversal.
|
|
4
|
+
*
|
|
5
|
+
* @template T - The node type (must include `id`).
|
|
6
|
+
* @template Key - The key pointing to nested children arrays (defaults to 'children').
|
|
7
|
+
*
|
|
8
|
+
* @param items - The root-level array of entities to index.
|
|
9
|
+
* @param nestedKey - The property name that contains child nodes within an entity (defaults to 'children').
|
|
10
|
+
* @param filter - Optional. A predicate to determine which nodes are included in the map (default: includes all).
|
|
11
|
+
* @param getId - Optional. A function to obtain the unique ID from a node (defaults to `node.id`).
|
|
12
|
+
* @returns A Map where each key is a node's ID and each value is the node, extended with an optional `parent` property.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const data = [
|
|
16
|
+
* { id: '1', name: 'Root', children: [
|
|
17
|
+
* { id: '2', name: 'Child 1' },
|
|
18
|
+
* { id: '3', name: 'Child 2', children: [
|
|
19
|
+
* { id: '4', name: 'Grandchild' }
|
|
20
|
+
* ]}
|
|
21
|
+
* ]}
|
|
22
|
+
* ];
|
|
23
|
+
* const index = generateReverseIndex({ items: data });
|
|
24
|
+
* // index.get('3') => { id: '3', name: 'Child 2', children: [...], parent: { id: '1', ... } }
|
|
25
|
+
* // index.get('4') => { id: '4', name: 'Grandchild', parent: { id: '3', ... } }
|
|
26
|
+
*/
|
|
27
|
+
var generateReverseIndex = ({ items, nestedKey = "children", filter = () => true, getId = (node) => node.id }) => {
|
|
28
|
+
const mapping = /* @__PURE__ */ new Map();
|
|
29
|
+
const dfs = (node) => {
|
|
30
|
+
if (filter(node)) mapping.set(getId(node), node);
|
|
31
|
+
if (nestedKey in node && Array.isArray(node[nestedKey])) node[nestedKey]?.forEach((it) => dfs({
|
|
32
|
+
...it,
|
|
33
|
+
parent: node
|
|
34
|
+
}));
|
|
35
|
+
};
|
|
36
|
+
items.forEach(dfs);
|
|
37
|
+
return mapping;
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
export { generateReverseIndex };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=generate-reverse-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-reverse-index.js","names":[],"sources":["../../src/helpers/generate-reverse-index.ts"],"sourcesContent":["/**\n * Builds a reverse index for fast ID-based lookup and attaches a `parent` property to each node for upward traversal.\n *\n * @template T - The node type (must include `id`).\n * @template Key - The key pointing to nested children arrays (defaults to 'children').\n *\n * @param items - The root-level array of entities to index.\n * @param nestedKey - The property name that contains child nodes within an entity (defaults to 'children').\n * @param filter - Optional. A predicate to determine which nodes are included in the map (default: includes all).\n * @param getId - Optional. A function to obtain the unique ID from a node (defaults to `node.id`).\n * @returns A Map where each key is a node's ID and each value is the node, extended with an optional `parent` property.\n *\n * @example\n * const data = [\n * { id: '1', name: 'Root', children: [\n * { id: '2', name: 'Child 1' },\n * { id: '3', name: 'Child 2', children: [\n * { id: '4', name: 'Grandchild' }\n * ]}\n * ]}\n * ];\n * const index = generateReverseIndex({ items: data });\n * // index.get('3') => { id: '3', name: 'Child 2', children: [...], parent: { id: '1', ... } }\n * // index.get('4') => { id: '4', name: 'Grandchild', parent: { id: '3', ... } }\n */\nexport const generateReverseIndex = <T extends { id: string } & Record<string, unknown>, Key extends string>({\n items,\n nestedKey = 'children' as Key,\n filter = () => true,\n getId = (node: T) => node.id,\n}: {\n items: T[]\n nestedKey?: Key\n filter?: (node: T) => boolean\n getId?: (node: T) => string\n}) => {\n // Create a mapping from id to node (with parent reference)\n const mapping = new Map<string, T & { parent?: T }>()\n\n const dfs = (node: T & { parent?: T }) => {\n if (filter(node)) {\n mapping.set(getId(node), node)\n }\n\n if (nestedKey in node && Array.isArray(node[nestedKey])) {\n // Recursively traverse children and add the current node as their parent\n node[nestedKey]?.forEach((it) => dfs({ ...it, parent: node }))\n }\n }\n\n items.forEach(dfs)\n\n return mapping\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,IAAa,wBAAgG,EAC3G,OACA,YAAY,YACZ,eAAe,MACf,SAAS,SAAY,KAAK,SAMtB;CAEJ,MAAM,0BAAU,IAAI,KAAiC;CAErD,MAAM,OAAO,SAA6B;AACxC,MAAI,OAAO,KAAK,CACd,SAAQ,IAAI,MAAM,KAAK,EAAE,KAAK;AAGhC,MAAI,aAAa,QAAQ,MAAM,QAAQ,KAAK,WAAW,CAErD,MAAK,YAAY,SAAS,OAAO,IAAI;GAAE,GAAG;GAAI,QAAQ;GAAM,CAAC,CAAC;;AAIlE,OAAM,QAAQ,IAAI;AAElB,QAAO"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/helpers/get-child-entry.ts
|
|
2
|
+
/**
|
|
3
|
+
* Recursively searches for and returns the first child node (including the given node itself)
|
|
4
|
+
* of a specific type within the provided node's subtree.
|
|
5
|
+
*
|
|
6
|
+
* @template Type - The type of node to search for.
|
|
7
|
+
* @param type - The node type to match.
|
|
8
|
+
* @param node - The root node to begin searching from.
|
|
9
|
+
* @returns The first child node of the specified type, or null if not found.
|
|
10
|
+
*/
|
|
11
|
+
var getChildEntry = (type, node) => {
|
|
12
|
+
if (node.type === type) return node;
|
|
13
|
+
if ("children" in node) for (const child of node.children ?? []) {
|
|
14
|
+
const result = getChildEntry(type, child);
|
|
15
|
+
if (result) return result;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
export { getChildEntry };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=get-child-entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-child-entry.js","names":[],"sources":["../../src/helpers/get-child-entry.ts"],"sourcesContent":["import type { TraversedEntry } from '@scalar/workspace-store/schemas/navigation'\n\n/**\n * Recursively searches for and returns the first child node (including the given node itself)\n * of a specific type within the provided node's subtree.\n *\n * @template Type - The type of node to search for.\n * @param type - The node type to match.\n * @param node - The root node to begin searching from.\n * @returns The first child node of the specified type, or null if not found.\n */\nexport const getChildEntry = <Type extends TraversedEntry['type']>(\n type: Type,\n node: TraversedEntry,\n): (TraversedEntry & { type: Type }) | null => {\n if (node.type === type) {\n return node as TraversedEntry & { type: Type }\n }\n\n if ('children' in node) {\n for (const child of node.children ?? []) {\n const result = getChildEntry(type, child)\n\n if (result) {\n return result\n }\n }\n }\n\n return null\n}\n"],"mappings":";;;;;;;;;;AAWA,IAAa,iBACX,MACA,SAC6C;AAC7C,KAAI,KAAK,SAAS,KAChB,QAAO;AAGT,KAAI,cAAc,KAChB,MAAK,MAAM,SAAS,KAAK,YAAY,EAAE,EAAE;EACvC,MAAM,SAAS,cAAc,MAAM,MAAM;AAEzC,MAAI,OACF,QAAO;;AAKb,QAAO"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/helpers/has-children.ts
|
|
2
|
+
/**
|
|
3
|
+
* Type guard to check if the given Item has a non-empty array of children.
|
|
4
|
+
* Returns true if `currentItem` has a `children` property that is an array with at least one element.
|
|
5
|
+
*/
|
|
6
|
+
var hasChildren = (currentItem) => {
|
|
7
|
+
return "children" in currentItem && Array.isArray(currentItem.children) && currentItem.children.length > 0;
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { hasChildren };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=has-children.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"has-children.js","names":[],"sources":["../../src/helpers/has-children.ts"],"sourcesContent":["import type { Item } from '@/types'\n\n/**\n * Type guard to check if the given Item has a non-empty array of children.\n * Returns true if `currentItem` has a `children` property that is an array with at least one element.\n */\nexport const hasChildren = (currentItem: Item): currentItem is Item & { children: Item[] } => {\n return 'children' in currentItem && Array.isArray(currentItem.children) && currentItem.children.length > 0\n}\n"],"mappings":";;;;;AAMA,IAAa,eAAe,gBAAkE;AAC5F,QAAO,cAAc,eAAe,MAAM,QAAQ,YAAY,SAAS,IAAI,YAAY,SAAS,SAAS"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { hasChildren } from "./has-children.js";
|
|
2
|
+
//#region src/helpers/is-sidebar-folder.ts
|
|
3
|
+
/**
|
|
4
|
+
* Determines if a sidebar item should be treated as a "folder",
|
|
5
|
+
* i.e. a collapsible group, depending on the layout, its type,
|
|
6
|
+
* whether it has children, and special slot rules.
|
|
7
|
+
*
|
|
8
|
+
* @param layout - The sidebar layout ('client' or 'reference')
|
|
9
|
+
* @param item - The sidebar item to check
|
|
10
|
+
* @param hasEmptySlot - True if there is an empty slot to display as a group (client layout only)
|
|
11
|
+
* @returns True if the item should be rendered as a folder/group node
|
|
12
|
+
*/
|
|
13
|
+
var isSidebarFolder = (layout, item, hasEmptySlot) => {
|
|
14
|
+
if (!hasChildren(item)) {
|
|
15
|
+
if (layout === "client" && hasEmptySlot) return item.type === "document" || item.type === "tag";
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (layout === "client") return true;
|
|
19
|
+
if (layout === "reference") return item.type !== "operation" && item.type !== "webhook";
|
|
20
|
+
return false;
|
|
21
|
+
};
|
|
22
|
+
//#endregion
|
|
23
|
+
export { isSidebarFolder };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=is-sidebar-folder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"is-sidebar-folder.js","names":[],"sources":["../../src/helpers/is-sidebar-folder.ts"],"sourcesContent":["import { hasChildren } from '@/helpers/has-children'\nimport type { Item, Layout } from '@/types'\n\n/**\n * Determines if a sidebar item should be treated as a \"folder\",\n * i.e. a collapsible group, depending on the layout, its type,\n * whether it has children, and special slot rules.\n *\n * @param layout - The sidebar layout ('client' or 'reference')\n * @param item - The sidebar item to check\n * @param hasEmptySlot - True if there is an empty slot to display as a group (client layout only)\n * @returns True if the item should be rendered as a folder/group node\n */\nexport const isSidebarFolder = (\n layout: Layout,\n item: Item,\n hasEmptySlot: boolean,\n): item is Item & { children?: Item[] } => {\n // If the item has no children, check if there is an empty slot.\n if (!hasChildren(item)) {\n // In 'client' layout, allow \"document\" or \"tag\" items to act as group if there's an empty slot\n if (layout === 'client' && hasEmptySlot) {\n return item.type === 'document' || item.type === 'tag'\n }\n // Otherwise, not a folder if it has no children\n return false\n }\n\n // 'client' layout: any item with children is a folder\n if (layout === 'client') {\n return true\n }\n\n // 'reference' layout: only non-'operation' and non-'webhook' are groups/folders\n if (layout === 'reference') {\n return item.type !== 'operation' && item.type !== 'webhook'\n }\n\n // Fallback: not a folder in other cases/layouts\n return false\n}\n"],"mappings":";;;;;;;;;;;;AAaA,IAAa,mBACX,QACA,MACA,iBACyC;AAEzC,KAAI,CAAC,YAAY,KAAK,EAAE;AAEtB,MAAI,WAAW,YAAY,aACzB,QAAO,KAAK,SAAS,cAAc,KAAK,SAAS;AAGnD,SAAO;;AAIT,KAAI,WAAW,SACb,QAAO;AAIT,KAAI,WAAW,YACb,QAAO,KAAK,SAAS,eAAe,KAAK,SAAS;AAIpD,QAAO"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region src/helpers/scroll-sidebar-to-top.ts
|
|
2
|
+
var DEFAULT_SCROLL_OFFSET_TOP = 100;
|
|
3
|
+
/**
|
|
4
|
+
* Returns the element that should be used for vertical position measurement.
|
|
5
|
+
*/
|
|
6
|
+
var getMeasurableElement = (element) => {
|
|
7
|
+
if (window.getComputedStyle(element).display !== "contents") return element;
|
|
8
|
+
/**
|
|
9
|
+
* `display: contents` does not render a layout box, so use the first child with
|
|
10
|
+
* a measurable offset to keep the scroll target aligned with what users see.
|
|
11
|
+
*/
|
|
12
|
+
for (const child of element.children) if (child instanceof HTMLElement && child.offsetParent !== null) return child;
|
|
13
|
+
return element;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Adds extra offset for heading rows so the selected item stays visible below labels.
|
|
17
|
+
*/
|
|
18
|
+
var getHeadingOffset = (element) => {
|
|
19
|
+
if (element.dataset.sidebarType !== "heading") return 0;
|
|
20
|
+
return element.querySelector(".sidebar-heading")?.offsetHeight ?? 0;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Computes an element top position relative to the provided scroll container.
|
|
24
|
+
*/
|
|
25
|
+
var getTopRelativeToScroller = (element, scroller) => {
|
|
26
|
+
let top = element.offsetTop;
|
|
27
|
+
let currentOffsetParent = element.offsetParent;
|
|
28
|
+
while (currentOffsetParent && currentOffsetParent !== scroller) {
|
|
29
|
+
top += currentOffsetParent.offsetTop;
|
|
30
|
+
currentOffsetParent = currentOffsetParent.offsetParent;
|
|
31
|
+
}
|
|
32
|
+
return top;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Scrolls the sidebar container so the requested item appears near the top.
|
|
36
|
+
*/
|
|
37
|
+
var scrollSidebarToTop = (id, offsetTop = DEFAULT_SCROLL_OFFSET_TOP) => {
|
|
38
|
+
if (typeof window === "undefined") return;
|
|
39
|
+
const element = document.querySelector(`[data-sidebar-id="${id}"]`);
|
|
40
|
+
const scroller = element?.closest(".custom-scroll, .custom-scrollbar") ?? null;
|
|
41
|
+
if (!element || !scroller) return;
|
|
42
|
+
const targetTop = getTopRelativeToScroller(getMeasurableElement(element), scroller) + getHeadingOffset(element) - offsetTop;
|
|
43
|
+
scroller.scrollTo({
|
|
44
|
+
top: targetTop > 0 ? targetTop : 0,
|
|
45
|
+
behavior: "smooth"
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
export { scrollSidebarToTop };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=scroll-sidebar-to-top.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-sidebar-to-top.js","names":[],"sources":["../../src/helpers/scroll-sidebar-to-top.ts"],"sourcesContent":["const DEFAULT_SCROLL_OFFSET_TOP = 100\n\n/**\n * Returns the element that should be used for vertical position measurement.\n */\nconst getMeasurableElement = (element: HTMLElement): HTMLElement => {\n if (window.getComputedStyle(element).display !== 'contents') {\n return element\n }\n\n /**\n * `display: contents` does not render a layout box, so use the first child with\n * a measurable offset to keep the scroll target aligned with what users see.\n */\n for (const child of element.children) {\n if (child instanceof HTMLElement && child.offsetParent !== null) {\n return child\n }\n }\n\n return element\n}\n\n/**\n * Adds extra offset for heading rows so the selected item stays visible below labels.\n */\nconst getHeadingOffset = (element: HTMLElement): number => {\n if (element.dataset.sidebarType !== 'heading') {\n return 0\n }\n\n return element.querySelector<HTMLElement>('.sidebar-heading')?.offsetHeight ?? 0\n}\n\n/**\n * Computes an element top position relative to the provided scroll container.\n */\nconst getTopRelativeToScroller = (element: HTMLElement, scroller: HTMLElement): number => {\n let top = element.offsetTop\n let currentOffsetParent = element.offsetParent as HTMLElement | null\n\n while (currentOffsetParent && currentOffsetParent !== scroller) {\n top += currentOffsetParent.offsetTop\n currentOffsetParent = currentOffsetParent.offsetParent as HTMLElement | null\n }\n\n return top\n}\n\n/**\n * Scrolls the sidebar container so the requested item appears near the top.\n */\nexport const scrollSidebarToTop = (id: string, offsetTop: number = DEFAULT_SCROLL_OFFSET_TOP): void => {\n if (typeof window === 'undefined') {\n return\n }\n\n const element = document.querySelector<HTMLElement>(`[data-sidebar-id=\"${id}\"]`)\n const scroller = element?.closest<HTMLElement>('.custom-scroll, .custom-scrollbar') ?? null\n if (!element || !scroller) {\n return\n }\n\n const measurableElement = getMeasurableElement(element)\n const itemTop = getTopRelativeToScroller(measurableElement, scroller)\n const itemOffset = getHeadingOffset(element)\n const targetTop = itemTop + itemOffset - offsetTop\n\n scroller.scrollTo({\n top: targetTop > 0 ? targetTop : 0,\n behavior: 'smooth',\n })\n}\n"],"mappings":";AAAA,IAAM,4BAA4B;;;;AAKlC,IAAM,wBAAwB,YAAsC;AAClE,KAAI,OAAO,iBAAiB,QAAQ,CAAC,YAAY,WAC/C,QAAO;;;;;AAOT,MAAK,MAAM,SAAS,QAAQ,SAC1B,KAAI,iBAAiB,eAAe,MAAM,iBAAiB,KACzD,QAAO;AAIX,QAAO;;;;;AAMT,IAAM,oBAAoB,YAAiC;AACzD,KAAI,QAAQ,QAAQ,gBAAgB,UAClC,QAAO;AAGT,QAAO,QAAQ,cAA2B,mBAAmB,EAAE,gBAAgB;;;;;AAMjF,IAAM,4BAA4B,SAAsB,aAAkC;CACxF,IAAI,MAAM,QAAQ;CAClB,IAAI,sBAAsB,QAAQ;AAElC,QAAO,uBAAuB,wBAAwB,UAAU;AAC9D,SAAO,oBAAoB;AAC3B,wBAAsB,oBAAoB;;AAG5C,QAAO;;;;;AAMT,IAAa,sBAAsB,IAAY,YAAoB,8BAAoC;AACrG,KAAI,OAAO,WAAW,YACpB;CAGF,MAAM,UAAU,SAAS,cAA2B,qBAAqB,GAAG,IAAI;CAChF,MAAM,WAAW,SAAS,QAAqB,oCAAoC,IAAI;AACvF,KAAI,CAAC,WAAW,CAAC,SACf;CAMF,MAAM,YAFU,yBADU,qBAAqB,QAAQ,EACK,SAAS,GAClD,iBAAiB,QAAQ,GACH;AAEzC,UAAS,SAAS;EAChB,KAAK,YAAY,IAAI,YAAY;EACjC,UAAU;EACX,CAAC"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { computed, ref, toValue } from "vue";
|
|
2
|
+
import { cva } from "@scalar/use-hooks/useBindCx";
|
|
3
|
+
//#region src/hooks/use-draggable.ts
|
|
4
|
+
/**
|
|
5
|
+
* Simple throttle function to avoid package dependencies
|
|
6
|
+
*/
|
|
7
|
+
var throttle = (callback, limit) => {
|
|
8
|
+
let wait = false;
|
|
9
|
+
return (...args) => {
|
|
10
|
+
if (wait) return;
|
|
11
|
+
callback(...args);
|
|
12
|
+
wait = true;
|
|
13
|
+
setTimeout(() => wait = false, limit);
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
/** Draggable class variants to apply to the draggable element */
|
|
17
|
+
var draggableVariants = cva({
|
|
18
|
+
base: "relative after:absolute after:inset-x-0 after:block after:bg-blue after:opacity-15 after:pointer-events-none after:rounded",
|
|
19
|
+
variants: { position: {
|
|
20
|
+
before: "after:-top-0.5 after:h-0.75",
|
|
21
|
+
after: "after:-bottom-0.5 after:h-0.75",
|
|
22
|
+
into: "after:inset-0"
|
|
23
|
+
} }
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* Shared state for drag and drop operations
|
|
27
|
+
* These are module-level refs so all draggable instances share the same state
|
|
28
|
+
*/
|
|
29
|
+
var draggingItem = ref(null);
|
|
30
|
+
var hoveredItem = ref(null);
|
|
31
|
+
/**
|
|
32
|
+
* Composable for handling drag and drop functionality
|
|
33
|
+
*/
|
|
34
|
+
function useDraggable(options) {
|
|
35
|
+
const { ceiling = .8, floor = .2, isDraggable = true, isDroppable = true, parentIds = [], id, onDragStart, onDragEnd } = options;
|
|
36
|
+
const parentId = computed(() => parentIds.at(-1) ?? null);
|
|
37
|
+
/** Check if isDroppable guard */
|
|
38
|
+
const _isDroppable = (offset) => typeof isDroppable === "function" ? isDroppable(draggingItem.value, {
|
|
39
|
+
id,
|
|
40
|
+
parentId: parentId.value,
|
|
41
|
+
offset
|
|
42
|
+
}) : toValue(isDroppable);
|
|
43
|
+
const handleDragStart = (ev) => {
|
|
44
|
+
if (!toValue(isDraggable) || !ev.dataTransfer || !(ev.target instanceof HTMLElement)) return;
|
|
45
|
+
ev.target.setAttribute("data-dragging", "true");
|
|
46
|
+
ev.dataTransfer.dropEffect = "move";
|
|
47
|
+
ev.dataTransfer.effectAllowed = "move";
|
|
48
|
+
const item = {
|
|
49
|
+
id,
|
|
50
|
+
parentId: parentId.value
|
|
51
|
+
};
|
|
52
|
+
draggingItem.value = item;
|
|
53
|
+
onDragStart?.(item);
|
|
54
|
+
};
|
|
55
|
+
const handleDragOver = throttle((ev) => {
|
|
56
|
+
if (!draggingItem.value || draggingItem.value.id === id || parentIds.includes(draggingItem.value?.id ?? "")) return;
|
|
57
|
+
const previousOffset = hoveredItem.value?.offset;
|
|
58
|
+
const height = ev.target.offsetHeight;
|
|
59
|
+
const _floor = floor * height;
|
|
60
|
+
const _ceiling = ceiling * height;
|
|
61
|
+
let offset = null;
|
|
62
|
+
if (ev.offsetY <= 0 && previousOffset && previousOffset !== "after") offset = previousOffset;
|
|
63
|
+
else if (ev.offsetY <= _floor) offset = "before";
|
|
64
|
+
else if (ev.offsetY >= _ceiling) offset = "after";
|
|
65
|
+
else if (ev.offsetY > _floor && ev.offsetY < _ceiling) offset = "into";
|
|
66
|
+
if (!_isDroppable(offset)) return;
|
|
67
|
+
hoveredItem.value = {
|
|
68
|
+
id,
|
|
69
|
+
parentId: parentId.value,
|
|
70
|
+
offset
|
|
71
|
+
};
|
|
72
|
+
}, 25);
|
|
73
|
+
const handleDragEnd = () => {
|
|
74
|
+
if (!hoveredItem.value || !draggingItem.value) return;
|
|
75
|
+
const _draggingItem = { ...draggingItem.value };
|
|
76
|
+
const _hoveredItem = { ...hoveredItem.value };
|
|
77
|
+
draggingItem.value = null;
|
|
78
|
+
hoveredItem.value = null;
|
|
79
|
+
document.querySelectorAll("[data-dragging]").forEach((el) => el.removeAttribute("data-dragging"));
|
|
80
|
+
if (_draggingItem.id === _hoveredItem.id) return;
|
|
81
|
+
onDragEnd?.(_draggingItem, _hoveredItem);
|
|
82
|
+
};
|
|
83
|
+
const draggableClass = computed(() => {
|
|
84
|
+
const position = id === hoveredItem.value?.id ? hoveredItem.value.offset : void 0;
|
|
85
|
+
if (!position) return "";
|
|
86
|
+
return draggableVariants({ position });
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
draggableAttrs: computed(() => ({
|
|
90
|
+
class: draggableClass.value || void 0,
|
|
91
|
+
draggable: toValue(isDraggable) ? true : void 0
|
|
92
|
+
})),
|
|
93
|
+
draggableEvents: {
|
|
94
|
+
dragend: handleDragEnd,
|
|
95
|
+
dragover: (ev) => {
|
|
96
|
+
ev.preventDefault();
|
|
97
|
+
ev.stopPropagation();
|
|
98
|
+
handleDragOver(ev);
|
|
99
|
+
},
|
|
100
|
+
dragstart: (ev) => {
|
|
101
|
+
ev.stopPropagation();
|
|
102
|
+
handleDragStart(ev);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
draggingItem,
|
|
106
|
+
hoveredItem
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
export { useDraggable };
|
|
111
|
+
|
|
112
|
+
//# sourceMappingURL=use-draggable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-draggable.js","names":[],"sources":["../../src/hooks/use-draggable.ts"],"sourcesContent":["import { cva } from '@scalar/use-hooks/useBindCx'\nimport { type MaybeRef, type Ref, computed, ref, toValue } from 'vue'\n\n/**\n * Drag offsets\n */\nexport type DragOffset =\n | 'before' // Insert before the hovered item\n | 'after' // Insert after the hovered item\n | 'into' // Drop into the hovered item\n | null\n\n/**\n * Item you are currently dragging over\n */\nexport type HoveredItem = {\n id: string\n parentId: string | null\n offset: DragOffset\n}\n\n/**\n * Item you are currently dragging\n */\nexport type DraggingItem = Omit<HoveredItem, 'offset'>\n\n/**\n * Simple throttle function to avoid package dependencies\n */\nconst throttle = <TArgs extends Array<unknown>>(\n callback: (...args: TArgs) => void,\n limit: number,\n): ((...args: TArgs) => void) => {\n let wait = false\n\n return (...args: TArgs) => {\n if (wait) {\n return\n }\n\n callback(...args)\n wait = true\n setTimeout(() => (wait = false), limit)\n }\n}\n\n/** Draggable class variants to apply to the draggable element */\nconst draggableVariants = cva({\n base: 'relative after:absolute after:inset-x-0 after:block after:bg-blue after:opacity-15 after:pointer-events-none after:rounded',\n variants: {\n position: {\n before: 'after:-top-0.5 after:h-0.75',\n after: 'after:-bottom-0.5 after:h-0.75',\n into: 'after:inset-0',\n } as const satisfies Record<NonNullable<DragOffset>, string>,\n },\n})\n\n/**\n * Shared state for drag and drop operations\n * These are module-level refs so all draggable instances share the same state\n */\nconst draggingItem = ref<DraggingItem | null>(null)\nconst hoveredItem = ref<HoveredItem | null>(null)\n\nexport type UseDraggableOptions = {\n /**\n * Upper threshold (gets multiplied with height)\n *\n * @default 0.8\n */\n ceiling?: number\n /**\n * Lower threshold (gets multiplied with height)\n *\n * @default 0.2\n */\n floor?: number\n /**\n * Enable dragging. Can be a reactive ref or computed.\n *\n * @default true\n */\n isDraggable?: MaybeRef<boolean>\n /**\n * Prevents items from being hovered and dropped into. Can be either a function or a boolean\n *\n * @default true\n */\n isDroppable?: MaybeRef<boolean> | ((draggingItem: DraggingItem, hoveredItem: HoveredItem) => boolean)\n /**\n * We pass an array of parents to make it easier to reverse traverse\n */\n parentIds?: string[]\n /**\n * ID for the current item\n */\n id: string\n /**\n * Callback when drag starts\n */\n onDragStart?: (draggingItem: DraggingItem) => void\n /**\n * Callback when drag ends\n */\n onDragEnd?: (draggingItem: DraggingItem, hoveredItem: HoveredItem) => void\n}\n\n/**\n * Composable for handling drag and drop functionality\n */\nexport function useDraggable(options: UseDraggableOptions) {\n const {\n ceiling = 0.8,\n floor = 0.2,\n isDraggable = true,\n isDroppable = true,\n parentIds = [],\n id,\n onDragStart,\n onDragEnd,\n } = options\n\n // The latest parentId in the arr should be the current parent\n const parentId = computed(() => parentIds.at(-1) ?? null)\n\n /** Check if isDroppable guard */\n const _isDroppable = (offset: DragOffset): boolean =>\n typeof isDroppable === 'function'\n ? isDroppable(draggingItem.value!, {\n id: id,\n parentId: parentId.value,\n offset,\n })\n : toValue(isDroppable)\n\n // Start dragging, we want to store the uid + parentUid\n const handleDragStart = (ev: DragEvent) => {\n if (!toValue(isDraggable) || !ev.dataTransfer || !(ev.target instanceof HTMLElement)) {\n return\n }\n\n ev.target.setAttribute('data-dragging', 'true')\n ev.dataTransfer.dropEffect = 'move'\n ev.dataTransfer.effectAllowed = 'move'\n\n // Store dragging item\n const item = { id: id, parentId: parentId.value }\n draggingItem.value = item\n onDragStart?.(item)\n }\n\n // On dragging over we decide which highlight to show\n const handleDragOver = throttle((ev: DragEvent) => {\n // Don't highlight if hovering over self or child\n if (!draggingItem.value || draggingItem.value.id === id || parentIds.includes(draggingItem.value?.id ?? '')) {\n return\n }\n\n const previousOffset = hoveredItem.value?.offset\n const height = (ev.target as HTMLDivElement).offsetHeight\n const _floor = floor * height\n const _ceiling = ceiling * height\n let offset: DragOffset = null\n\n // handle negative offset to be previous offset\n if (ev.offsetY <= 0 && previousOffset && previousOffset !== 'after') {\n offset = previousOffset\n }\n // Above\n else if (ev.offsetY <= _floor) {\n offset = 'before'\n }\n // Below\n else if (ev.offsetY >= _ceiling) {\n offset = 'after'\n }\n // between\n else if (ev.offsetY > _floor && ev.offsetY < _ceiling) {\n offset = 'into'\n }\n\n // Hover guard\n if (!_isDroppable(offset)) {\n return\n }\n\n hoveredItem.value = { id: id, parentId: parentId.value, offset }\n }, 25)\n\n const handleDragEnd = () => {\n if (!hoveredItem.value || !draggingItem.value) {\n return\n }\n\n const _draggingItem = { ...draggingItem.value }\n const _hoveredItem = { ...hoveredItem.value }\n\n // Remove hover and dragging\n draggingItem.value = null\n hoveredItem.value = null\n document.querySelectorAll('[data-dragging]').forEach((el) => el.removeAttribute('data-dragging'))\n\n if (_draggingItem.id === _hoveredItem.id) {\n return\n }\n\n onDragEnd?.(_draggingItem, _hoveredItem)\n }\n\n const draggableClass = computed(() => {\n const position = id === hoveredItem.value?.id ? hoveredItem.value.offset : undefined\n\n if (!position) {\n return ''\n }\n\n return draggableVariants({ position })\n })\n\n /**\n * Props object to bind to the draggable element.\n * Contains the class and draggable attribute.\n */\n const draggableAttrs = computed(() => ({\n class: draggableClass.value || undefined,\n // Only set the draggable attribute if isDraggable is true\n draggable: toValue(isDraggable) ? true : undefined,\n }))\n\n return {\n /**\n * Props object to bind to the draggable element.\n * Contains the class and draggable attribute.\n */\n draggableAttrs,\n /**\n * Event handlers object to bind to the draggable element.\n * Contains dragend, dragover, and dragstart handlers with proper event prevention.\n */\n draggableEvents: {\n dragend: handleDragEnd,\n dragover: (ev: DragEvent) => {\n ev.preventDefault()\n ev.stopPropagation()\n handleDragOver(ev)\n },\n dragstart: (ev: DragEvent) => {\n ev.stopPropagation()\n handleDragStart(ev)\n },\n },\n draggingItem: draggingItem as Readonly<Ref<DraggingItem | null>>,\n hoveredItem: hoveredItem as Readonly<Ref<HoveredItem | null>>,\n }\n}\n"],"mappings":";;;;;;AA6BA,IAAM,YACJ,UACA,UAC+B;CAC/B,IAAI,OAAO;AAEX,SAAQ,GAAG,SAAgB;AACzB,MAAI,KACF;AAGF,WAAS,GAAG,KAAK;AACjB,SAAO;AACP,mBAAkB,OAAO,OAAQ,MAAM;;;;AAK3C,IAAM,oBAAoB,IAAI;CAC5B,MAAM;CACN,UAAU,EACR,UAAU;EACR,QAAQ;EACR,OAAO;EACP,MAAM;EACP,EACF;CACF,CAAC;;;;;AAMF,IAAM,eAAe,IAAyB,KAAK;AACnD,IAAM,cAAc,IAAwB,KAAK;;;;AAgDjD,SAAgB,aAAa,SAA8B;CACzD,MAAM,EACJ,UAAU,IACV,QAAQ,IACR,cAAc,MACd,cAAc,MACd,YAAY,EAAE,EACd,IACA,aACA,cACE;CAGJ,MAAM,WAAW,eAAe,UAAU,GAAG,GAAG,IAAI,KAAK;;CAGzD,MAAM,gBAAgB,WACpB,OAAO,gBAAgB,aACnB,YAAY,aAAa,OAAQ;EAC3B;EACJ,UAAU,SAAS;EACnB;EACD,CAAC,GACF,QAAQ,YAAY;CAG1B,MAAM,mBAAmB,OAAkB;AACzC,MAAI,CAAC,QAAQ,YAAY,IAAI,CAAC,GAAG,gBAAgB,EAAE,GAAG,kBAAkB,aACtE;AAGF,KAAG,OAAO,aAAa,iBAAiB,OAAO;AAC/C,KAAG,aAAa,aAAa;AAC7B,KAAG,aAAa,gBAAgB;EAGhC,MAAM,OAAO;GAAM;GAAI,UAAU,SAAS;GAAO;AACjD,eAAa,QAAQ;AACrB,gBAAc,KAAK;;CAIrB,MAAM,iBAAiB,UAAU,OAAkB;AAEjD,MAAI,CAAC,aAAa,SAAS,aAAa,MAAM,OAAO,MAAM,UAAU,SAAS,aAAa,OAAO,MAAM,GAAG,CACzG;EAGF,MAAM,iBAAiB,YAAY,OAAO;EAC1C,MAAM,SAAU,GAAG,OAA0B;EAC7C,MAAM,SAAS,QAAQ;EACvB,MAAM,WAAW,UAAU;EAC3B,IAAI,SAAqB;AAGzB,MAAI,GAAG,WAAW,KAAK,kBAAkB,mBAAmB,QAC1D,UAAS;WAGF,GAAG,WAAW,OACrB,UAAS;WAGF,GAAG,WAAW,SACrB,UAAS;WAGF,GAAG,UAAU,UAAU,GAAG,UAAU,SAC3C,UAAS;AAIX,MAAI,CAAC,aAAa,OAAO,CACvB;AAGF,cAAY,QAAQ;GAAM;GAAI,UAAU,SAAS;GAAO;GAAQ;IAC/D,GAAG;CAEN,MAAM,sBAAsB;AAC1B,MAAI,CAAC,YAAY,SAAS,CAAC,aAAa,MACtC;EAGF,MAAM,gBAAgB,EAAE,GAAG,aAAa,OAAO;EAC/C,MAAM,eAAe,EAAE,GAAG,YAAY,OAAO;AAG7C,eAAa,QAAQ;AACrB,cAAY,QAAQ;AACpB,WAAS,iBAAiB,kBAAkB,CAAC,SAAS,OAAO,GAAG,gBAAgB,gBAAgB,CAAC;AAEjG,MAAI,cAAc,OAAO,aAAa,GACpC;AAGF,cAAY,eAAe,aAAa;;CAG1C,MAAM,iBAAiB,eAAe;EACpC,MAAM,WAAW,OAAO,YAAY,OAAO,KAAK,YAAY,MAAM,SAAS,KAAA;AAE3E,MAAI,CAAC,SACH,QAAO;AAGT,SAAO,kBAAkB,EAAE,UAAU,CAAC;GACtC;AAYF,QAAO;EAKL,gBAXqB,gBAAgB;GACrC,OAAO,eAAe,SAAS,KAAA;GAE/B,WAAW,QAAQ,YAAY,GAAG,OAAO,KAAA;GAC1C,EAAE;EAYD,iBAAiB;GACf,SAAS;GACT,WAAW,OAAkB;AAC3B,OAAG,gBAAgB;AACnB,OAAG,iBAAiB;AACpB,mBAAe,GAAG;;GAEpB,YAAY,OAAkB;AAC5B,OAAG,iBAAiB;AACpB,oBAAgB,GAAG;;GAEtB;EACa;EACD;EACd"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import './style.css';
|
|
2
|
-
export { default as HttpMethod } from './components/HttpMethod.vue';
|
|
3
|
-
export { default as ScalarSidebar } from './components/ScalarSidebar.vue';
|
|
4
|
-
export { default as SidebarHttpBadge } from './components/SidebarHttpBadge.vue';
|
|
5
|
-
export { default as SidebarItem } from './components/SidebarItem.vue';
|
|
6
|
-
export { type SidebarState, createSidebarState, } from './helpers/create-sidebar-state';
|
|
7
|
-
export { generateReverseIndex } from './helpers/generate-reverse-index';
|
|
8
|
-
export { getChildEntry } from './helpers/get-child-entry';
|
|
9
|
-
export { scrollSidebarToTop } from './helpers/scroll-sidebar-to-top';
|
|
10
|
-
export { type DragOffset, type DraggingItem, type HoveredItem, useDraggable, } from './hooks/use-draggable';
|
|
2
|
+
export { default as HttpMethod } from './components/HttpMethod.vue.js';
|
|
3
|
+
export { default as ScalarSidebar } from './components/ScalarSidebar.vue.js';
|
|
4
|
+
export { default as SidebarHttpBadge } from './components/SidebarHttpBadge.vue.js';
|
|
5
|
+
export { default as SidebarItem } from './components/SidebarItem.vue.js';
|
|
6
|
+
export { type SidebarState, createSidebarState, } from './helpers/create-sidebar-state.js';
|
|
7
|
+
export { generateReverseIndex } from './helpers/generate-reverse-index.js';
|
|
8
|
+
export { getChildEntry } from './helpers/get-child-entry.js';
|
|
9
|
+
export { scrollSidebarToTop } from './helpers/scroll-sidebar-to-top.js';
|
|
10
|
+
export { type DragOffset, type DraggingItem, type HoveredItem, useDraggable, } from './hooks/use-draggable.js';
|
|
11
11
|
export type { Item } from './types.ts';
|
|
12
12
|
//# sourceMappingURL=index.d.ts.map
|