vitepress-plugin-collapse 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pengzhanbo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # vitepress-plugin-collapse
2
+
3
+ Render collapsible sections in your VitePress site.
4
+
5
+ 在 VitePress 中渲染可折叠的内容区块。
6
+
7
+ ## Usage
8
+
9
+ ### With Vitepress-tuck
10
+
11
+ **Installation:**
12
+
13
+ ```bash
14
+ # npm
15
+ npm install -D vitepress-tuck vitepress-plugin-collapse
16
+ # pnpm
17
+ pnpm add -D vitepress-tuck vitepress-plugin-collapse
18
+ # yarn
19
+ yarn add -D vitepress-tuck vitepress-plugin-collapse
20
+ ```
21
+
22
+ **Configuration:**
23
+
24
+ ```ts
25
+ // .vitepress/config.ts
26
+ import collapse from 'vitepress-plugin-collapse'
27
+ import { defineConfig } from 'vitepress-tuck'
28
+
29
+ export default defineConfig({
30
+ plugins: [collapse()],
31
+ })
32
+ ```
33
+
34
+ ### With Vitepress
35
+
36
+ **Installation:**
37
+
38
+ ```bash
39
+ # npm
40
+ npm install -D vitepress-plugin-collapse
41
+ # pnpm
42
+ pnpm add -D vitepress-plugin-collapse
43
+ # yarn
44
+ yarn add -D vitepress-plugin-collapse
45
+ ```
46
+
47
+ **Configuration:**
48
+
49
+ ```ts
50
+ // .vitepress/config.ts
51
+ import { defineConfig } from 'vitepress'
52
+ import { collapseMarkdownPlugin } from 'vitepress-plugin-collapse'
53
+
54
+ export default defineConfig({
55
+ markdown: {
56
+ config: (md) => {
57
+ md.use(collapseMarkdownPlugin)
58
+ },
59
+ },
60
+ })
61
+ ```
62
+
63
+ ```ts
64
+ // .vitepress/theme/index.ts
65
+ import type { Theme } from 'vitepress'
66
+ import { enhanceAppWithCollapse } from 'vitepress-plugin-collapse/client'
67
+ import 'vitepress-plugin-collapse/style.css'
68
+ import DefaultTheme from 'vitepress/theme'
69
+
70
+ export default {
71
+ extends: DefaultTheme,
72
+ enhanceApp(ctx) {
73
+ enhanceAppWithCollapse(ctx)
74
+ },
75
+ } satisfies Theme
76
+ ```
77
+
78
+ ## Syntax
79
+
80
+ Use `::: collapse` container with a list to create collapsible sections. Each
81
+ list item becomes a collapse panel: the first line is the title, and the
82
+ following indented content is the panel body.
83
+
84
+ 使用 `::: collapse` 容器配合列表来创建可折叠区块。每个列表项成为一个折叠面板:
85
+ 第一行为标题,后续缩进内容为面板正文。
86
+
87
+ ### Basic
88
+
89
+ ```md
90
+ ::: collapse
91
+ - Title 1
92
+ Content of item 1.
93
+
94
+ - Title 2
95
+ Content of item 2.
96
+ :::
97
+ ```
98
+
99
+ ### Accordion
100
+
101
+ Add `accordion` to enable accordion mode — only one item can be expanded at a
102
+ time. Use `expand` to expand the first item by default when no explicit `:+`
103
+ flag is set.
104
+
105
+ 添加 `accordion` 启用手风琴模式 —— 同时只能展开一项。使用 `expand` 可在没有
106
+ 显式 `:+` 标记时默认展开第一项。
107
+
108
+ ```md
109
+ ::: collapse accordion expand
110
+ - Question 1
111
+ Answer 1.
112
+
113
+ - Question 2
114
+ Answer 2.
115
+ :::
116
+ ```
117
+
118
+ ### Card Style
119
+
120
+ Add `card` to render the container with a bordered, rounded card style.
121
+
122
+ 添加 `card` 以带边框、圆角的卡片样式渲染容器。
123
+
124
+ ```md
125
+ ::: collapse accordion card
126
+ - Question 1
127
+ Answer 1.
128
+
129
+ - Question 2
130
+ Answer 2.
131
+ :::
132
+ ```
133
+
134
+ ### Per-item Expand Flag
135
+
136
+ Prefix a title with `:+` to expand an item, or `:-` to collapse it. In
137
+ accordion mode, only the first `:+` flag takes effect.
138
+
139
+ 在标题前添加 `:+` 展开该项,或 `:-` 收起该项。手风琴模式下只有第一个 `:+`
140
+ 标记生效。
141
+
142
+ ```md
143
+ ::: collapse
144
+ - :+ Expanded by default
145
+ Content.
146
+
147
+ - :- Collapsed by default
148
+ Content.
149
+
150
+ - No flag, follows container `expand` attribute
151
+ Content.
152
+ :::
153
+ ```
154
+
155
+ ### Container Attributes
156
+
157
+ | Attribute | Type | Description |
158
+ | ----------- | --------- | ------------------------------------------------------------------------- |
159
+ | `accordion` | `boolean` | Enable accordion mode (only one item expanded at a time) / 启用手风琴模式 |
160
+ | `card` | `boolean` | Render with card style (bordered and rounded) / 使用卡片样式 |
161
+ | `expand` | `boolean` | Default expanded state / 默认展开状态 |
162
+
163
+ ### Item Flags
164
+
165
+ | Flag | Description |
166
+ | ---- | --------------------------------------------------------------------------------------------------- |
167
+ | `:+` | Expand this item (only the first one wins in accordion mode) / 展开该项(手风琴模式下仅第一个生效) |
168
+ | `:-` | Collapse this item / 收起该项 |
@@ -0,0 +1,71 @@
1
+ import { EnhanceAppContext } from "vitepress/client";
2
+
3
+ //#region src/client/VPCollapse.vue.d.ts
4
+ type __VLS_Props$1 = {
5
+ accordion?: boolean;
6
+ card?: boolean;
7
+ index?: number;
8
+ };
9
+ declare var __VLS_1$1: {};
10
+ type __VLS_Slots$1 = {} & {
11
+ default?: (props: typeof __VLS_1$1) => any;
12
+ };
13
+ declare const __VLS_base$1: import("vue").DefineComponent<__VLS_Props$1, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props$1> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
+ declare const __VLS_export$1: __VLS_WithSlots$1<typeof __VLS_base$1, __VLS_Slots$1>;
15
+ declare const _default: typeof __VLS_export$1;
16
+ type __VLS_WithSlots$1<T, S> = T & {
17
+ new (): {
18
+ $slots: S;
19
+ };
20
+ };
21
+ //#endregion
22
+ //#region src/client/VPCollapseItem.vue.d.ts
23
+ type __VLS_Props = {
24
+ expand?: boolean;
25
+ index: number;
26
+ };
27
+ declare var __VLS_1: {}, __VLS_9: {};
28
+ type __VLS_Slots = {} & {
29
+ title?: (props: typeof __VLS_1) => any;
30
+ } & {
31
+ default?: (props: typeof __VLS_9) => any;
32
+ };
33
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
34
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
35
+ declare const _default$1: typeof __VLS_export;
36
+ type __VLS_WithSlots<T, S> = T & {
37
+ new (): {
38
+ $slots: S;
39
+ };
40
+ };
41
+ //#endregion
42
+ //#region src/client/index.d.ts
43
+ /**
44
+ * Register `VPCollapse` and `VPCollapseItem` globally when using this plugin
45
+ * with VitePress directly (without `vitepress-tuck`).
46
+ *
47
+ * 当不使用 `vitepress-tuck` 而直接在 VitePress 中使用本插件时,
48
+ * 用于全局注册 `VPCollapse` 和 `VPCollapseItem` 组件。
49
+ *
50
+ * @param param0 - The VitePress enhance app context / VitePress 增强应用上下文
51
+ * @param param0.app - The Vue app instance to register components on / 要注册组件的 Vue 应用实例
52
+ * @example
53
+ * ```ts
54
+ * // .vitepress/theme/index.ts
55
+ * import type { Theme } from 'vitepress'
56
+ * import DefaultTheme from 'vitepress/theme'
57
+ * import { enhanceAppWithCollapse } from 'vitepress-plugin-collapse/client'
58
+ *
59
+ * export default {
60
+ * extends: DefaultTheme,
61
+ * enhanceApp(ctx) {
62
+ * enhanceAppWithCollapse(ctx)
63
+ * },
64
+ * } satisfies Theme
65
+ * ```
66
+ */
67
+ declare function enhanceAppWithCollapse({
68
+ app
69
+ }: EnhanceAppContext): void;
70
+ //#endregion
71
+ export { _default as VPCollapse, _default$1 as VPCollapseItem, enhanceAppWithCollapse };
@@ -0,0 +1,150 @@
1
+ import "../style.css";
2
+ import { createElementBlock, createElementVNode, createVNode, defineComponent, inject, normalizeClass, openBlock, provide, ref, renderSlot, unref, vShow, watch, withCtx, withDirectives, withKeys, withModifiers } from "vue";
3
+ import { FadeInExpandTransition } from "vitepress-plugin-toolkit/client";
4
+ //#region src/client/constants.ts
5
+ /**
6
+ * Injection key for the collapse container context, shared between
7
+ * `<VPCollapse>` and its child `<VPCollapseItem>` components.
8
+ *
9
+ * 折叠面板容器上下文的注入键,在 `<VPCollapse>` 与其子级 `<VPCollapseItem>`
10
+ * 组件之间共享。
11
+ *
12
+ * - `accordion`: whether accordion mode is enabled / 是否启用手风琴模式
13
+ * - `index`: the currently expanded item index (writable ref), used to
14
+ * coordinate mutual exclusion in accordion mode / 当前展开项的索引(可写
15
+ * ref),用于在手风琴模式下协调互斥展开
16
+ */
17
+ const COLLAPSE_KEY = Symbol(import.meta.env.DEV ? "collapse" : "");
18
+ //#endregion
19
+ //#region src/client/VPCollapse.vue
20
+ const _sfc_main = /*@__PURE__*/ defineComponent({
21
+ __name: "VPCollapse",
22
+ props: {
23
+ accordion: {
24
+ type: Boolean,
25
+ default: false
26
+ },
27
+ card: { type: Boolean },
28
+ index: {}
29
+ },
30
+ setup(__props) {
31
+ /**
32
+ * Root component of the collapse container.
33
+ *
34
+ * 折叠面板容器的根组件。
35
+ *
36
+ * Renders a list of `<VPCollapseItem>` children and provides the collapse
37
+ * context via `provide`. In accordion mode, `index` tracks the currently
38
+ * expanded item so children can coordinate mutual exclusion.
39
+ *
40
+ * 渲染一组 `<VPCollapseItem>` 子组件,并通过 `provide` 提供折叠面板上下文。
41
+ * 手风琴模式下,`index` 用于跟踪当前展开项,使子组件能够协调互斥展开。
42
+ *
43
+ * @prop accordion - Enable accordion mode / 是否启用手风琴模式
44
+ * @prop card - Render with card style / 是否使用卡片样式
45
+ * @prop index - Default expanded item index (accordion mode) / 默认展开项索引(手风琴模式)
46
+ */
47
+ const currentIndex = ref(__props.index);
48
+ provide(COLLAPSE_KEY, {
49
+ accordion: __props.accordion,
50
+ index: currentIndex
51
+ });
52
+ return (_ctx, _cache) => {
53
+ return openBlock(), createElementBlock("div", { class: normalizeClass(["vp-collapse", { card: __props.card }]) }, [renderSlot(_ctx.$slots, "default")], 2);
54
+ };
55
+ }
56
+ });
57
+ //#endregion
58
+ //#region src/client/VPCollapseItem.vue
59
+ const _hoisted_1 = ["aria-expanded", "onKeydown"];
60
+ const _hoisted_2 = { class: "vp-collapse-title" };
61
+ const _hoisted_3 = { class: "vp-collapse-content" };
62
+ const _hoisted_4 = { class: "vp-collapse-content-inner" };
63
+ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
64
+ __name: "VPCollapseItem",
65
+ props: {
66
+ expand: { type: Boolean },
67
+ index: {}
68
+ },
69
+ setup(__props) {
70
+ /**
71
+ * Single collapse item, must be used inside `<VPCollapse>`.
72
+ *
73
+ * 单个折叠面板项,必须在 `<VPCollapse>` 内部使用。
74
+ *
75
+ * - Renders a clickable header (with the `title` slot) and a collapsible
76
+ * content area (default slot).
77
+ * - In accordion mode, the item reads the shared `index` from the collapse
78
+ * context and toggles it; expanding one item collapses all others.
79
+ * - Outside accordion mode, each item toggles its own `expanded` state
80
+ * independently.
81
+ *
82
+ * - 渲染可点击的头部(使用 `title` 插槽)和可折叠的内容区域(默认插槽)。
83
+ * - 手风琴模式下,该项从折叠面板上下文读取共享的 `index` 并进行切换;
84
+ * 展开一项会自动收起其他项。
85
+ * - 非手风琴模式下,每项独立切换自身的 `expanded` 状态。
86
+ *
87
+ * @prop expand - Whether this item is expanded (ignored in accordion mode) / 是否展开该项(手风琴模式下忽略)
88
+ * @prop index - Zero-based index within the parent collapse / 在父级折叠面板中的索引(从 0 开始)
89
+ */
90
+ const collapse = inject(COLLAPSE_KEY);
91
+ if (import.meta.env.DEV && !collapse) throw new Error("<VPCollapseItem /> must be used inside <VPCollapse />");
92
+ const expanded = ref(collapse?.accordion && typeof collapse.index.value !== "undefined" ? __props.index === collapse.index.value : __props.expand);
93
+ if (collapse?.accordion) watch(collapse?.index, () => {
94
+ expanded.value = collapse?.index.value === __props.index;
95
+ });
96
+ function toggle() {
97
+ if (collapse?.accordion) if (collapse.index.value === __props.index && expanded.value) expanded.value = false;
98
+ else {
99
+ collapse.index.value = __props.index;
100
+ expanded.value = true;
101
+ }
102
+ else expanded.value = !expanded.value;
103
+ }
104
+ return (_ctx, _cache) => {
105
+ return openBlock(), createElementBlock("div", { class: normalizeClass(["vp-collapse-item", { expanded: expanded.value }]) }, [createElementVNode("div", {
106
+ class: "vp-collapse-header",
107
+ tabindex: "0",
108
+ role: "button",
109
+ "aria-expanded": expanded.value ? "true" : "false",
110
+ onClick: toggle,
111
+ onKeydown: [withKeys(withModifiers(toggle, ["prevent"]), ["enter"]), withKeys(withModifiers(toggle, ["prevent"]), ["space"])]
112
+ }, [_cache[0] || (_cache[0] = createElementVNode("span", { class: "vpi-chevron-right" }, null, -1)), createElementVNode("p", _hoisted_2, [renderSlot(_ctx.$slots, "title")])], 40, _hoisted_1), createVNode(unref(FadeInExpandTransition), null, {
113
+ default: withCtx(() => [withDirectives(createElementVNode("div", _hoisted_3, [createElementVNode("div", _hoisted_4, [renderSlot(_ctx.$slots, "default")])], 512), [[vShow, expanded.value]])]),
114
+ _: 3
115
+ })], 2);
116
+ };
117
+ }
118
+ });
119
+ //#endregion
120
+ //#region src/client/index.ts
121
+ /**
122
+ * Register `VPCollapse` and `VPCollapseItem` globally when using this plugin
123
+ * with VitePress directly (without `vitepress-tuck`).
124
+ *
125
+ * 当不使用 `vitepress-tuck` 而直接在 VitePress 中使用本插件时,
126
+ * 用于全局注册 `VPCollapse` 和 `VPCollapseItem` 组件。
127
+ *
128
+ * @param param0 - The VitePress enhance app context / VitePress 增强应用上下文
129
+ * @param param0.app - The Vue app instance to register components on / 要注册组件的 Vue 应用实例
130
+ * @example
131
+ * ```ts
132
+ * // .vitepress/theme/index.ts
133
+ * import type { Theme } from 'vitepress'
134
+ * import DefaultTheme from 'vitepress/theme'
135
+ * import { enhanceAppWithCollapse } from 'vitepress-plugin-collapse/client'
136
+ *
137
+ * export default {
138
+ * extends: DefaultTheme,
139
+ * enhanceApp(ctx) {
140
+ * enhanceAppWithCollapse(ctx)
141
+ * },
142
+ * } satisfies Theme
143
+ * ```
144
+ */
145
+ function enhanceAppWithCollapse({ app }) {
146
+ app.component("VPCollapse", _sfc_main);
147
+ app.component("VPCollapseItem", _sfc_main$1);
148
+ }
149
+ //#endregion
150
+ export { _sfc_main as VPCollapse, _sfc_main$1 as VPCollapseItem, enhanceAppWithCollapse };
@@ -0,0 +1,71 @@
1
+ import { EnhanceAppContext } from "vitepress/client";
2
+
3
+ //#region src/client/VPCollapse.vue.d.ts
4
+ type __VLS_Props$1 = {
5
+ accordion?: boolean;
6
+ card?: boolean;
7
+ index?: number;
8
+ };
9
+ declare var __VLS_1$1: {};
10
+ type __VLS_Slots$1 = {} & {
11
+ default?: (props: typeof __VLS_1$1) => any;
12
+ };
13
+ declare const __VLS_base$1: import("vue").DefineComponent<__VLS_Props$1, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props$1> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
+ declare const __VLS_export$1: __VLS_WithSlots$1<typeof __VLS_base$1, __VLS_Slots$1>;
15
+ declare const _default: typeof __VLS_export$1;
16
+ type __VLS_WithSlots$1<T, S> = T & {
17
+ new (): {
18
+ $slots: S;
19
+ };
20
+ };
21
+ //#endregion
22
+ //#region src/client/VPCollapseItem.vue.d.ts
23
+ type __VLS_Props = {
24
+ expand?: boolean;
25
+ index: number;
26
+ };
27
+ declare var __VLS_1: {}, __VLS_9: {};
28
+ type __VLS_Slots = {} & {
29
+ title?: (props: typeof __VLS_1) => any;
30
+ } & {
31
+ default?: (props: typeof __VLS_9) => any;
32
+ };
33
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
34
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
35
+ declare const _default$1: typeof __VLS_export;
36
+ type __VLS_WithSlots<T, S> = T & {
37
+ new (): {
38
+ $slots: S;
39
+ };
40
+ };
41
+ //#endregion
42
+ //#region src/client/index.d.ts
43
+ /**
44
+ * Register `VPCollapse` and `VPCollapseItem` globally when using this plugin
45
+ * with VitePress directly (without `vitepress-tuck`).
46
+ *
47
+ * 当不使用 `vitepress-tuck` 而直接在 VitePress 中使用本插件时,
48
+ * 用于全局注册 `VPCollapse` 和 `VPCollapseItem` 组件。
49
+ *
50
+ * @param param0 - The VitePress enhance app context / VitePress 增强应用上下文
51
+ * @param param0.app - The Vue app instance to register components on / 要注册组件的 Vue 应用实例
52
+ * @example
53
+ * ```ts
54
+ * // .vitepress/theme/index.ts
55
+ * import type { Theme } from 'vitepress'
56
+ * import DefaultTheme from 'vitepress/theme'
57
+ * import { enhanceAppWithCollapse } from 'vitepress-plugin-collapse/client'
58
+ *
59
+ * export default {
60
+ * extends: DefaultTheme,
61
+ * enhanceApp(ctx) {
62
+ * enhanceAppWithCollapse(ctx)
63
+ * },
64
+ * } satisfies Theme
65
+ * ```
66
+ */
67
+ declare function enhanceAppWithCollapse({
68
+ app
69
+ }: EnhanceAppContext): void;
70
+ //#endregion
71
+ export { _default as VPCollapse, _default$1 as VPCollapseItem, enhanceAppWithCollapse };
@@ -0,0 +1,157 @@
1
+ import { createVNode, defineComponent, inject, mergeProps, provide, ref, renderSlot, unref, useSSRContext, vShow, watch, withCtx, withDirectives } from "vue";
2
+ import { ssrRenderAttr, ssrRenderAttrs, ssrRenderComponent, ssrRenderSlot, ssrRenderStyle } from "vue/server-renderer";
3
+ import { FadeInExpandTransition } from "vitepress-plugin-toolkit/client";
4
+ //#region src/client/constants.ts
5
+ /**
6
+ * Injection key for the collapse container context, shared between
7
+ * `<VPCollapse>` and its child `<VPCollapseItem>` components.
8
+ *
9
+ * 折叠面板容器上下文的注入键,在 `<VPCollapse>` 与其子级 `<VPCollapseItem>`
10
+ * 组件之间共享。
11
+ *
12
+ * - `accordion`: whether accordion mode is enabled / 是否启用手风琴模式
13
+ * - `index`: the currently expanded item index (writable ref), used to
14
+ * coordinate mutual exclusion in accordion mode / 当前展开项的索引(可写
15
+ * ref),用于在手风琴模式下协调互斥展开
16
+ */
17
+ const COLLAPSE_KEY = Symbol(import.meta.env.DEV ? "collapse" : "");
18
+ //#endregion
19
+ //#region src/client/VPCollapse.vue
20
+ const _sfc_main = /*@__PURE__*/ defineComponent({
21
+ __name: "VPCollapse",
22
+ __ssrInlineRender: true,
23
+ props: {
24
+ accordion: {
25
+ type: Boolean,
26
+ default: false
27
+ },
28
+ card: { type: Boolean },
29
+ index: {}
30
+ },
31
+ setup(__props) {
32
+ /**
33
+ * Root component of the collapse container.
34
+ *
35
+ * 折叠面板容器的根组件。
36
+ *
37
+ * Renders a list of `<VPCollapseItem>` children and provides the collapse
38
+ * context via `provide`. In accordion mode, `index` tracks the currently
39
+ * expanded item so children can coordinate mutual exclusion.
40
+ *
41
+ * 渲染一组 `<VPCollapseItem>` 子组件,并通过 `provide` 提供折叠面板上下文。
42
+ * 手风琴模式下,`index` 用于跟踪当前展开项,使子组件能够协调互斥展开。
43
+ *
44
+ * @prop accordion - Enable accordion mode / 是否启用手风琴模式
45
+ * @prop card - Render with card style / 是否使用卡片样式
46
+ * @prop index - Default expanded item index (accordion mode) / 默认展开项索引(手风琴模式)
47
+ */
48
+ const currentIndex = ref(__props.index);
49
+ provide(COLLAPSE_KEY, {
50
+ accordion: __props.accordion,
51
+ index: currentIndex
52
+ });
53
+ return (_ctx, _push, _parent, _attrs) => {
54
+ _push(`<div${ssrRenderAttrs(mergeProps({ class: ["vp-collapse", { card: __props.card }] }, _attrs))}>`);
55
+ ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent);
56
+ _push(`</div>`);
57
+ };
58
+ }
59
+ });
60
+ const _sfc_setup$1 = _sfc_main.setup;
61
+ _sfc_main.setup = (props, ctx) => {
62
+ const ssrContext = useSSRContext();
63
+ (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/client/VPCollapse.vue");
64
+ return _sfc_setup$1 ? _sfc_setup$1(props, ctx) : void 0;
65
+ };
66
+ //#endregion
67
+ //#region src/client/VPCollapseItem.vue
68
+ const _sfc_main$1 = /*@__PURE__*/ defineComponent({
69
+ __name: "VPCollapseItem",
70
+ __ssrInlineRender: true,
71
+ props: {
72
+ expand: { type: Boolean },
73
+ index: {}
74
+ },
75
+ setup(__props) {
76
+ /**
77
+ * Single collapse item, must be used inside `<VPCollapse>`.
78
+ *
79
+ * 单个折叠面板项,必须在 `<VPCollapse>` 内部使用。
80
+ *
81
+ * - Renders a clickable header (with the `title` slot) and a collapsible
82
+ * content area (default slot).
83
+ * - In accordion mode, the item reads the shared `index` from the collapse
84
+ * context and toggles it; expanding one item collapses all others.
85
+ * - Outside accordion mode, each item toggles its own `expanded` state
86
+ * independently.
87
+ *
88
+ * - 渲染可点击的头部(使用 `title` 插槽)和可折叠的内容区域(默认插槽)。
89
+ * - 手风琴模式下,该项从折叠面板上下文读取共享的 `index` 并进行切换;
90
+ * 展开一项会自动收起其他项。
91
+ * - 非手风琴模式下,每项独立切换自身的 `expanded` 状态。
92
+ *
93
+ * @prop expand - Whether this item is expanded (ignored in accordion mode) / 是否展开该项(手风琴模式下忽略)
94
+ * @prop index - Zero-based index within the parent collapse / 在父级折叠面板中的索引(从 0 开始)
95
+ */
96
+ const collapse = inject(COLLAPSE_KEY);
97
+ if (import.meta.env.DEV && !collapse) throw new Error("<VPCollapseItem /> must be used inside <VPCollapse />");
98
+ const expanded = ref(collapse?.accordion && typeof collapse.index.value !== "undefined" ? __props.index === collapse.index.value : __props.expand);
99
+ if (collapse?.accordion) watch(collapse?.index, () => {
100
+ expanded.value = collapse?.index.value === __props.index;
101
+ });
102
+ return (_ctx, _push, _parent, _attrs) => {
103
+ _push(`<div${ssrRenderAttrs(mergeProps({ class: ["vp-collapse-item", { expanded: expanded.value }] }, _attrs))}><div class="vp-collapse-header" tabindex="0" role="button"${ssrRenderAttr("aria-expanded", expanded.value ? "true" : "false")}><span class="vpi-chevron-right"></span><p class="vp-collapse-title">`);
104
+ ssrRenderSlot(_ctx.$slots, "title", {}, null, _push, _parent);
105
+ _push(`</p></div>`);
106
+ _push(ssrRenderComponent(unref(FadeInExpandTransition), null, {
107
+ default: withCtx((_, _push, _parent, _scopeId) => {
108
+ if (_push) {
109
+ _push(`<div class="vp-collapse-content" style="${ssrRenderStyle(expanded.value ? null : { display: "none" })}"${_scopeId}><div class="vp-collapse-content-inner"${_scopeId}>`);
110
+ ssrRenderSlot(_ctx.$slots, "default", {}, null, _push, _parent, _scopeId);
111
+ _push(`</div></div>`);
112
+ } else return [withDirectives(createVNode("div", { class: "vp-collapse-content" }, [createVNode("div", { class: "vp-collapse-content-inner" }, [renderSlot(_ctx.$slots, "default")])], 512), [[vShow, expanded.value]])];
113
+ }),
114
+ _: 3
115
+ }, _parent));
116
+ _push(`</div>`);
117
+ };
118
+ }
119
+ });
120
+ const _sfc_setup = _sfc_main$1.setup;
121
+ _sfc_main$1.setup = (props, ctx) => {
122
+ const ssrContext = useSSRContext();
123
+ (ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("src/client/VPCollapseItem.vue");
124
+ return _sfc_setup ? _sfc_setup(props, ctx) : void 0;
125
+ };
126
+ //#endregion
127
+ //#region src/client/index.ts
128
+ /**
129
+ * Register `VPCollapse` and `VPCollapseItem` globally when using this plugin
130
+ * with VitePress directly (without `vitepress-tuck`).
131
+ *
132
+ * 当不使用 `vitepress-tuck` 而直接在 VitePress 中使用本插件时,
133
+ * 用于全局注册 `VPCollapse` 和 `VPCollapseItem` 组件。
134
+ *
135
+ * @param param0 - The VitePress enhance app context / VitePress 增强应用上下文
136
+ * @param param0.app - The Vue app instance to register components on / 要注册组件的 Vue 应用实例
137
+ * @example
138
+ * ```ts
139
+ * // .vitepress/theme/index.ts
140
+ * import type { Theme } from 'vitepress'
141
+ * import DefaultTheme from 'vitepress/theme'
142
+ * import { enhanceAppWithCollapse } from 'vitepress-plugin-collapse/client'
143
+ *
144
+ * export default {
145
+ * extends: DefaultTheme,
146
+ * enhanceApp(ctx) {
147
+ * enhanceAppWithCollapse(ctx)
148
+ * },
149
+ * } satisfies Theme
150
+ * ```
151
+ */
152
+ function enhanceAppWithCollapse({ app }) {
153
+ app.component("VPCollapse", _sfc_main);
154
+ app.component("VPCollapseItem", _sfc_main$1);
155
+ }
156
+ //#endregion
157
+ export { _sfc_main as VPCollapse, _sfc_main$1 as VPCollapseItem, enhanceAppWithCollapse };
@@ -0,0 +1,97 @@
1
+ @import url("vitepress-plugin-toolkit/styles/transition/fade-in-height-expand.css");
2
+
3
+ .vp-collapse {
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: 16px;
7
+ margin-block: 16px;
8
+ margin-inline: 0;
9
+ }
10
+
11
+ .vp-collapse.card {
12
+ padding-block-end: 16px;
13
+ border: solid 1px var(--vp-c-divider);
14
+ border-radius: 8px;
15
+ }
16
+
17
+ .vp-collapse-item {
18
+ position: relative;
19
+ display: flex;
20
+ flex-direction: column;
21
+ padding-block-start: 16px;
22
+ border-top: solid 1px var(--vp-c-divider);
23
+ }
24
+
25
+ .vp-collapse-item:first-child,
26
+ .vp-collapse.card .vp-collapse-item {
27
+ border-top: none;
28
+ }
29
+
30
+ .vp-collapse:not(.card) .vp-collapse-item:first-child {
31
+ padding-block-start: 0;
32
+ }
33
+
34
+ .vp-collapse.card .vp-collapse-item::after {
35
+ position: absolute;
36
+ right: 16px;
37
+ bottom: -16px;
38
+ left: 16px;
39
+ display: block;
40
+ height: 1px;
41
+ content: "";
42
+ background-color: var(--vp-c-divider);
43
+ }
44
+
45
+ .vp-collapse.card .vp-collapse-item:last-child::after {
46
+ display: none;
47
+ }
48
+
49
+ .vp-collapse-header {
50
+ display: flex;
51
+ gap: 6px;
52
+ align-items: first baseline;
53
+ font-size: 16px;
54
+ font-weight: 600;
55
+ cursor: pointer;
56
+ }
57
+
58
+ .vp-collapse-header .vpi-chevron-right {
59
+ position: relative;
60
+ top: 4px;
61
+ width: 20px;
62
+ height: 20px;
63
+ color: var(--vp-c-text-3);
64
+ transition: transform 0.25s ease-in-out;
65
+ transform: rotate(0deg);
66
+ }
67
+
68
+ .vp-collapse.card .vp-collapse-header {
69
+ gap: 0;
70
+ padding-inline: 4px 16px;
71
+ }
72
+
73
+ .vp-collapse-item.expanded .vpi-chevron-right {
74
+ transform: rotate(90deg);
75
+ }
76
+
77
+ .vp-collapse-header .vp-collapse-title {
78
+ flex: 1 2;
79
+ margin: 0;
80
+ }
81
+
82
+ .vp-collapse-content-inner {
83
+ padding-block-start: 12px;
84
+ padding-inline-start: 24px;
85
+ }
86
+
87
+ .vp-collapse.card .vp-collapse-content-inner {
88
+ padding-inline-end: 16px;
89
+ }
90
+
91
+ .vp-collapse-content-inner > *:first-child {
92
+ margin-top: 0;
93
+ }
94
+
95
+ .vp-collapse-content-inner > *:last-child {
96
+ margin-bottom: 0;
97
+ }
@@ -0,0 +1,56 @@
1
+ import { PluginSimple } from "markdown-it";
2
+
3
+ //#region src/node/plugin.d.ts
4
+ /**
5
+ * Collapse plugin factory.
6
+ *
7
+ * 折叠面板插件工厂函数。
8
+ *
9
+ * Registers the `::: collapse` markdown-it container and declares the
10
+ * `VPCollapse` and `VPCollapseItem` Vue components for auto-import. When used
11
+ * with `defineConfig` from `vitepress-tuck`, the components are auto-imported
12
+ * on demand — no manual `enhanceApp` registration is required.
13
+ *
14
+ * 注册 `::: collapse` markdown-it 容器,并声明 `VPCollapse` 和 `VPCollapseItem`
15
+ * Vue 组件以供自动导入。与 `vitepress-tuck` 的 `defineConfig` 配合使用时,
16
+ * 组件会按需自动导入,无需手动在 `enhanceApp` 中注册。
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * // .vitepress/config.ts
21
+ * import { defineConfig } from 'vitepress-tuck'
22
+ * import collapse from 'vitepress-plugin-collapse'
23
+ *
24
+ * export default defineConfig({
25
+ * plugins: [collapse()],
26
+ * })
27
+ * ```
28
+ */
29
+ declare const collapse: (options?: unknown) => import("vitepress-tuck").VitepressPlugin;
30
+ //#endregion
31
+ //#region src/node/markdown.d.ts
32
+ /**
33
+ * Collapse plugin - Enable collapse container in markdown-it.
34
+ *
35
+ * 折叠面板插件 - 在 markdown-it 中启用折叠面板容器。
36
+ *
37
+ * Registers the `::: collapse` container and custom renderer rules for
38
+ * `collapse_item_*` tokens. The container is converted into a `<VPCollapse>`
39
+ * Vue component, and each list item is converted into a `<VPCollapseItem>`.
40
+ *
41
+ * 注册 `::: collapse` 容器以及 `collapse_item_*` 令牌的自定义渲染规则。
42
+ * 容器会被转换为 `<VPCollapse>` Vue 组件,每个列表项会被转换为 `<VPCollapseItem>`。
43
+ *
44
+ * @param md - Markdown instance / Markdown 实例
45
+ * @example
46
+ * ```ts
47
+ * import MarkdownIt from 'markdown-it'
48
+ * import { collapseMarkdownPlugin } from 'vitepress-plugin-collapse'
49
+ *
50
+ * const md = new MarkdownIt()
51
+ * md.use(collapseMarkdownPlugin)
52
+ * ```
53
+ */
54
+ declare const collapseMarkdownPlugin: PluginSimple;
55
+ //#endregion
56
+ export { collapse, collapse as default, collapseMarkdownPlugin };
@@ -0,0 +1,174 @@
1
+ import { definePlugin } from "vitepress-tuck";
2
+ import { createContainerPlugin, resolveAttrs, stringifyAttrs } from "vitepress-plugin-toolkit";
3
+ //#region src/node/markdown.ts
4
+ /**
5
+ * Collapse plugin - Enable collapse container in markdown-it.
6
+ *
7
+ * 折叠面板插件 - 在 markdown-it 中启用折叠面板容器。
8
+ *
9
+ * Registers the `::: collapse` container and custom renderer rules for
10
+ * `collapse_item_*` tokens. The container is converted into a `<VPCollapse>`
11
+ * Vue component, and each list item is converted into a `<VPCollapseItem>`.
12
+ *
13
+ * 注册 `::: collapse` 容器以及 `collapse_item_*` 令牌的自定义渲染规则。
14
+ * 容器会被转换为 `<VPCollapse>` Vue 组件,每个列表项会被转换为 `<VPCollapseItem>`。
15
+ *
16
+ * @param md - Markdown instance / Markdown 实例
17
+ * @example
18
+ * ```ts
19
+ * import MarkdownIt from 'markdown-it'
20
+ * import { collapseMarkdownPlugin } from 'vitepress-plugin-collapse'
21
+ *
22
+ * const md = new MarkdownIt()
23
+ * md.use(collapseMarkdownPlugin)
24
+ * ```
25
+ */
26
+ const collapseMarkdownPlugin = (md) => {
27
+ createContainerPlugin(md, "collapse", {
28
+ before: (info, tokens, index) => {
29
+ const attrs = resolveAttrs(info);
30
+ const idx = parseCollapse(tokens, index, attrs);
31
+ const { accordion, card } = attrs;
32
+ return `<VPCollapse${stringifyAttrs({
33
+ accordion,
34
+ card,
35
+ index: idx
36
+ })}>`;
37
+ },
38
+ after: () => `</VPCollapse>`
39
+ });
40
+ md.renderer.rules.collapse_item_open = (tokens, idx) => {
41
+ const { expand, index } = tokens[idx].meta;
42
+ return `<VPCollapseItem${stringifyAttrs({
43
+ expand,
44
+ index
45
+ })}>`;
46
+ };
47
+ md.renderer.rules.collapse_item_close = () => "</VPCollapseItem>";
48
+ md.renderer.rules.collapse_item_title_open = () => "<template #title>";
49
+ md.renderer.rules.collapse_item_title_close = () => "</template>";
50
+ };
51
+ /**
52
+ * Parse collapse tokens and transform list items into collapse items.
53
+ *
54
+ * 解析折叠面板令牌,并将列表项转换为折叠面板项。
55
+ *
56
+ * Walks the token stream starting after the `container_collapse_open` token,
57
+ * hides the wrapping list tokens, and rewrites root-level list item tokens
58
+ * into `collapse_item_*` tokens. Each title's leading `:+` or `:-` flag is
59
+ * stripped and used to determine the item's expand state:
60
+ * - `:+` expands the item (in accordion mode, only the first one wins).
61
+ * - `:-` collapses the item.
62
+ * - No flag falls back to the container's `expand` attribute.
63
+ *
64
+ * 从 `container_collapse_open` 令牌之后开始遍历令牌流,隐藏外层列表令牌,
65
+ * 并将根级列表项令牌重写为 `collapse_item_*` 令牌。每个标题前缀的 `:+` 或 `:-`
66
+ * 标记会被移除,用于决定该项的展开状态:
67
+ * - `:+` 展开该项(手风琴模式下仅第一个生效)。
68
+ * - `:-` 收起该项。
69
+ * - 无标记时回退到容器的 `expand` 属性。
70
+ *
71
+ * @param tokens - Token array / 令牌数组
72
+ * @param index - Start index (the `container_collapse_open` token) / 起始索引(`container_collapse_open` 令牌)
73
+ * @param attrs - Collapse metadata parsed from container info / 从容器信息解析得到的折叠面板元数据
74
+ * @returns Default expanded index for accordion mode, or undefined / 手风琴模式下默认展开项的索引,无则返回 undefined
75
+ */
76
+ function parseCollapse(tokens, index, attrs) {
77
+ const listStack = [];
78
+ let idx = -1;
79
+ let defaultIndex;
80
+ let hashExpand = false;
81
+ for (let i = index + 1; i < tokens.length; i++) {
82
+ const token = tokens[i];
83
+ if (token.type === "container_collapse_close") break;
84
+ if (token.type === "bullet_list_open" || token.type === "ordered_list_open") {
85
+ listStack.push(0);
86
+ if (listStack.length === 1) token.hidden = true;
87
+ } else if (token.type === "bullet_list_close" || token.type === "ordered_list_close") {
88
+ listStack.pop();
89
+ if (listStack.length === 0) token.hidden = true;
90
+ } else if (token.type === "list_item_open") {
91
+ if (listStack.length === 1) {
92
+ token.type = "collapse_item_open";
93
+ tokens[i + 1].type = "collapse_item_title_open";
94
+ tokens[i + 3].type = "collapse_item_title_close";
95
+ idx++;
96
+ const firstToken = tokens[i + 2].children?.[0];
97
+ let flag = "";
98
+ let expand;
99
+ if (firstToken?.type === "text") firstToken.content = firstToken.content.trim().replace(/^:[+\-]\s*/, (match) => {
100
+ flag = match.trim();
101
+ return "";
102
+ });
103
+ if (attrs.accordion) {
104
+ if (!hashExpand && flag === ":+") {
105
+ expand = hashExpand = true;
106
+ defaultIndex = idx;
107
+ }
108
+ } else if (flag === ":+") expand = true;
109
+ else if (flag === ":-") expand = false;
110
+ else expand = !!attrs.expand;
111
+ token.meta = {
112
+ index: idx,
113
+ expand
114
+ };
115
+ }
116
+ } else if (token.type === "list_item_close") {
117
+ if (listStack.length === 1) token.type = "collapse_item_close";
118
+ }
119
+ }
120
+ if (attrs.accordion && attrs.expand && !hashExpand) defaultIndex = 0;
121
+ return defaultIndex;
122
+ }
123
+ //#endregion
124
+ //#region src/node/plugin.ts
125
+ /**
126
+ * Collapse plugin factory.
127
+ *
128
+ * 折叠面板插件工厂函数。
129
+ *
130
+ * Registers the `::: collapse` markdown-it container and declares the
131
+ * `VPCollapse` and `VPCollapseItem` Vue components for auto-import. When used
132
+ * with `defineConfig` from `vitepress-tuck`, the components are auto-imported
133
+ * on demand — no manual `enhanceApp` registration is required.
134
+ *
135
+ * 注册 `::: collapse` markdown-it 容器,并声明 `VPCollapse` 和 `VPCollapseItem`
136
+ * Vue 组件以供自动导入。与 `vitepress-tuck` 的 `defineConfig` 配合使用时,
137
+ * 组件会按需自动导入,无需手动在 `enhanceApp` 中注册。
138
+ *
139
+ * @example
140
+ * ```ts
141
+ * // .vitepress/config.ts
142
+ * import { defineConfig } from 'vitepress-tuck'
143
+ * import collapse from 'vitepress-plugin-collapse'
144
+ *
145
+ * export default defineConfig({
146
+ * plugins: [collapse()],
147
+ * })
148
+ * ```
149
+ */
150
+ const collapse = definePlugin(() => ({
151
+ name: "vitepress-plugin-collapse",
152
+ componentResolver: ["VPCollapse", "VPCollapseItem"],
153
+ markdown: { config: (md) => {
154
+ md.use(collapseMarkdownPlugin);
155
+ } }
156
+ }));
157
+ //#endregion
158
+ //#region src/node/index.ts
159
+ /**
160
+ * Node entry for vitepress-plugin-collapse.
161
+ *
162
+ * vitepress-plugin-collapse 的 Node 端入口。
163
+ *
164
+ * Re-exports the markdown-it plugin, the `collapse` plugin factory (as the
165
+ * default export) for use with `vitepress-tuck`'s `defineConfig`.
166
+ *
167
+ * 再导出 markdown-it 插件及 `collapse` 插件工厂函数(作为默认导出),
168
+ * 供 `vitepress-tuck` 的 `defineConfig` 使用。
169
+ *
170
+ * @module
171
+ */
172
+ var node_default = collapse;
173
+ //#endregion
174
+ export { collapse, collapseMarkdownPlugin, node_default as default };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "vitepress-plugin-collapse",
3
+ "type": "module",
4
+ "version": "0.7.0",
5
+ "description": "Render collapsible sections in your VitePress site.",
6
+ "author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
7
+ "license": "MIT",
8
+ "homepage": "https://tuck.pengzhanbo.cn/plugins/collapse",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/pengzhanbo/vitepress-tuck.git",
12
+ "directory": "packages/plugin-collapse"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/pengzhanbo/vitepress-tuck/issues"
16
+ },
17
+ "keywords": [
18
+ "vitepress",
19
+ "vitepress-plugin",
20
+ "collapse"
21
+ ],
22
+ "exports": {
23
+ ".": "./dist/node/index.js",
24
+ "./client": {
25
+ "browser": "./dist/client/browser/index.js",
26
+ "default": "./dist/client/ssr/index.js"
27
+ },
28
+ "./style.css": "./dist/client/style.css"
29
+ },
30
+ "module": "./dist/node/index.js",
31
+ "types": "./dist/node/index.d.ts",
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "peerDependencies": {
36
+ "vitepress": "^1.6.4 || ^2.0.0-alpha.17",
37
+ "vue": "^3.5.0"
38
+ },
39
+ "dependencies": {
40
+ "vitepress-plugin-toolkit": "0.7.0",
41
+ "vitepress-tuck": "0.7.0"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "scripts": {
47
+ "clean": "rimraf --glob ./dist",
48
+ "dev": "pnpm '/(tsdown|copy):watch/'",
49
+ "build": "pnpm tsdown && pnpm copy",
50
+ "copy": "cpx \"src/**/*.css\" dist",
51
+ "copy:watch": "pnpm copy -w",
52
+ "tsdown": "tsdown --config-loader unrun",
53
+ "tsdown:watch": "pnpm tsdown -w"
54
+ }
55
+ }