@todovue/tv-toc 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +14 -26
- package/dist/tv-toc.cjs.js +1 -1
- package/dist/tv-toc.css +1 -1
- package/dist/tv-toc.es.js +167 -46
- package/package.json +9 -8
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
A lightweight Vue 3 component to render a Table of Contents (TOC) for your articles or documentation, with smooth scrolling and nested sections support.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@todovue/tv-toc)
|
|
8
|
-
[](https://app.netlify.com/projects/tv-toc/deploys)
|
|
9
8
|
[](https://www.npmjs.com/package/@todovue/tv-toc)
|
|
10
9
|
[](https://www.npmjs.com/package/@todovue/tv-toc)
|
|
11
10
|

|
|
@@ -15,15 +14,14 @@ A lightweight Vue 3 component to render a Table of Contents (TOC) for your artic
|
|
|
15
14
|

|
|
16
15
|

|
|
17
16
|
|
|
18
|
-
> Demo: https://
|
|
17
|
+
> Demo: https://ui.todovue.blog/toc
|
|
19
18
|
|
|
20
|
-
---
|
|
21
19
|
## Table of Contents
|
|
22
20
|
- [Features](#features)
|
|
23
21
|
- [Installation](#installation)
|
|
24
22
|
- [Usage of Styles](#usage-of-styles)
|
|
25
23
|
- [Quick Start (SPA)](#quick-start-spa)
|
|
26
|
-
- [Nuxt
|
|
24
|
+
- [Nuxt 4 / SSR Usage](#nuxt-4--ssr-usage)
|
|
27
25
|
- [Component Registration Options](#component-registration-options)
|
|
28
26
|
- [Props](#props)
|
|
29
27
|
- [Composable: useToc](#composable-usetoc)
|
|
@@ -34,7 +32,6 @@ A lightweight Vue 3 component to render a Table of Contents (TOC) for your artic
|
|
|
34
32
|
- [Contributing](#contributing)
|
|
35
33
|
- [License](#license)
|
|
36
34
|
|
|
37
|
-
---
|
|
38
35
|
## Features
|
|
39
36
|
- Simple and focused Table of Contents (TOC) component for Vue 3.
|
|
40
37
|
- Supports nested sections via children links.
|
|
@@ -43,7 +40,6 @@ A lightweight Vue 3 component to render a Table of Contents (TOC) for your artic
|
|
|
43
40
|
- Works in SPA (Vite, Vue CLI) and Nuxt 3 (with client-side rendering constraints).
|
|
44
41
|
- Ships with minimal, customizable styles.
|
|
45
42
|
|
|
46
|
-
---
|
|
47
43
|
## Installation
|
|
48
44
|
Using npm:
|
|
49
45
|
```bash
|
|
@@ -58,7 +54,6 @@ Using pnpm:
|
|
|
58
54
|
pnpm add @todovue/tv-toc
|
|
59
55
|
```
|
|
60
56
|
|
|
61
|
-
---
|
|
62
57
|
## Usage of Styles
|
|
63
58
|
|
|
64
59
|
### Vue/Vite (SPA)
|
|
@@ -87,7 +82,6 @@ export default defineNuxtConfig({
|
|
|
87
82
|
})
|
|
88
83
|
```
|
|
89
84
|
|
|
90
|
-
---
|
|
91
85
|
## Quick Start (SPA)
|
|
92
86
|
Global registration (main.js / main.ts):
|
|
93
87
|
```js
|
|
@@ -140,8 +134,7 @@ const toc = {
|
|
|
140
134
|
</template>
|
|
141
135
|
```
|
|
142
136
|
|
|
143
|
-
|
|
144
|
-
## Nuxt 3 / SSR Usage
|
|
137
|
+
## Nuxt 4 / SSR Usage
|
|
145
138
|
Create a plugin file: `plugins/tv-toc.client.ts` (client-only because it uses `document` and `history` under the hood when scrolling):
|
|
146
139
|
```ts
|
|
147
140
|
import { defineNuxtPlugin } from '#app'
|
|
@@ -170,7 +163,6 @@ import { TvToc } from '@todovue/tv-toc'
|
|
|
170
163
|
</template>
|
|
171
164
|
```
|
|
172
165
|
|
|
173
|
-
---
|
|
174
166
|
## Component Registration Options
|
|
175
167
|
| Approach | When to use |
|
|
176
168
|
|-----------------------------------------------|-----------------------------------|
|
|
@@ -178,11 +170,14 @@ import { TvToc } from '@todovue/tv-toc'
|
|
|
178
170
|
| Local named import `{ TvToc }` | Isolated/code-split contexts |
|
|
179
171
|
| Direct default import `import TvToc from ...` | Single use or manual registration |
|
|
180
172
|
|
|
181
|
-
---
|
|
182
173
|
## Props
|
|
183
|
-
| Name
|
|
184
|
-
|
|
185
|
-
| toc
|
|
174
|
+
| Name | Type | Default | Description | Required |
|
|
175
|
+
|-----------------|---------|------------|----------------------------------------------------------------------------|----------|
|
|
176
|
+
| toc | Object | - | TOC configuration: title and list of links (with optional nested children) | `true` |
|
|
177
|
+
| marker | Boolean | `false` | Whether to display a visual marker for the active item. | `false` |
|
|
178
|
+
| collapsible | Boolean | `false` | Whether sublists can be collapsed/expanded. | `false` |
|
|
179
|
+
| activeClass | String | `'active'` | Custom CSS class for the active item. | `false` |
|
|
180
|
+
| observerOptions | Object | `{}` | options to pass to the IntersectionObserver (rootMargin, threshold, etc). | `false` |
|
|
186
181
|
|
|
187
182
|
### `toc` shape
|
|
188
183
|
```ts
|
|
@@ -201,16 +196,17 @@ type Toc = {
|
|
|
201
196
|
- `links`: Array of top-level sections.
|
|
202
197
|
- `id`: Must match the `id` attribute of the target heading in your content.
|
|
203
198
|
- `text`: Label shown in the TOC.
|
|
204
|
-
- `children`: Optional array of
|
|
199
|
+
- `children`: Optional array of subsections, rendered as nested list.
|
|
205
200
|
|
|
206
|
-
---
|
|
207
201
|
## Composable: `useToc`
|
|
208
202
|
This composable is used internally by `TvToc` but can also be imported directly if needed.
|
|
209
203
|
|
|
210
204
|
```ts
|
|
211
205
|
import { useToc } from '@todovue/tv-toc'
|
|
212
206
|
|
|
213
|
-
const { formatId, scrollToId } = useToc(
|
|
207
|
+
const { formatId, scrollToId } = useToc(links, {
|
|
208
|
+
rootMargin: '0px 0px -50% 0px'
|
|
209
|
+
})
|
|
214
210
|
```
|
|
215
211
|
|
|
216
212
|
### API
|
|
@@ -221,7 +217,6 @@ const { formatId, scrollToId } = useToc()
|
|
|
221
217
|
|
|
222
218
|
> Note: `scrollToId` accesses `document` and `history`, so it should run only in the browser (e.g. in event handlers or inside `onMounted`).
|
|
223
219
|
|
|
224
|
-
---
|
|
225
220
|
## Customization (Styles)
|
|
226
221
|
The component ships with minimal default styles, exposed through the built CSS file and scoped CSS classes.
|
|
227
222
|
|
|
@@ -255,13 +250,11 @@ You can override these styles in your own global stylesheet:
|
|
|
255
250
|
|
|
256
251
|
If you are using SCSS, you can also rely on your own design tokens and overrides. The package itself internally uses SCSS (see `src/assets/scss/_variables.scss` and `src/assets/scss/style.scss`).
|
|
257
252
|
|
|
258
|
-
---
|
|
259
253
|
## SSR Notes
|
|
260
254
|
- The component can be rendered on the server (template is static), but scrolling behavior uses browser APIs.
|
|
261
255
|
- `scrollToId` uses `document.getElementById` and `history.pushState`; these are only invoked in event handlers on the client.
|
|
262
256
|
- When using Nuxt 3, prefer registering `TvToc` in a `*.client.ts` plugin or wrap usages in `<client-only>` to avoid hydration edge cases in environments with stricter SSR.
|
|
263
257
|
|
|
264
|
-
---
|
|
265
258
|
## Examples
|
|
266
259
|
This repository includes a small demo application built with Vite.
|
|
267
260
|
|
|
@@ -270,7 +263,6 @@ This repository includes a small demo application built with Vite.
|
|
|
270
263
|
|
|
271
264
|
To run the demo locally, see the [Development](#development) section.
|
|
272
265
|
|
|
273
|
-
---
|
|
274
266
|
## Development
|
|
275
267
|
```bash
|
|
276
268
|
git clone https://github.com/TODOvue/tv-toc.git
|
|
@@ -286,15 +278,11 @@ To build the standalone demo used for documentation:
|
|
|
286
278
|
npm run build:demo
|
|
287
279
|
```
|
|
288
280
|
|
|
289
|
-
---
|
|
290
281
|
## Contributing
|
|
291
282
|
PRs and issues are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
|
292
283
|
|
|
293
|
-
---
|
|
294
284
|
## License
|
|
295
285
|
MIT © TODOvue
|
|
296
286
|
|
|
297
|
-
---
|
|
298
287
|
### Attributions
|
|
299
288
|
Crafted for the TODOvue component ecosystem
|
|
300
|
-
|
package/dist/tv-toc.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("vue"),B=(n=[],a={})=>{const p=e.ref(null),h=e.ref(null);let d=null;const k=r=>`#${r}`,y=r=>{const c=document.getElementById(r);c&&(c.scrollIntoView({behavior:"smooth"}),history.pushState(null,null,`#${r}`))},u=r=>{const c=[];return r.forEach(s=>{c.push({id:s.id,parentId:null}),s.children&&s.children.forEach(v=>{c.push({id:v.id,parentId:s.id})})}),c};return{formatId:k,scrollToId:y,activeId:p,activeParentId:h,setupObserver:()=>{if(typeof window>"u"||!n.length)return;const r=u(n),c=r.map(({id:s})=>document.getElementById(s)).filter(s=>s!==null);c.length&&(d=new IntersectionObserver(s=>{s.forEach(v=>{if(v.isIntersecting){const m=v.target.id,t=r.find(l=>l.id===m);t&&(p.value=m,h.value=t.parentId,history.replaceState(null,null,`#${m}`))}})},{rootMargin:"-20% 0px -70% 0px",threshold:0,...a}),c.forEach(s=>d.observe(s)))},cleanup:()=>{d&&(d.disconnect(),d=null)}}},b={class:"tv-toc"},w={key:0,class:"tv-toc-progress-container"},C={key:1,class:"tv-toc-title"},I={class:"tv-toc-list"},T={class:"tv-toc-item-content"},S=["href","onClick"],x=["onClick"],V={class:"tv-toc-sublist"},N=["href","onClick"],O={__name:"TvToc",props:{toc:{type:Object,required:!0},marker:{type:Boolean,default:!1},activeClass:{type:String,default:"active"},observerOptions:{type:Object,default:()=>({})},collapsible:{type:Boolean,default:!1}},setup(n){const a=n,{scrollToId:p,activeId:h,activeParentId:d,setupObserver:k,cleanup:y}=B(a.toc?.links||[],a.observerOptions),u=e.ref(new Set),E=t=>{const l=new Set(u.value);l.has(t)?l.delete(t):l.add(t),u.value=l},g=t=>!a.collapsible||u.value.has(t),r=t=>{p(t)},c=t=>h.value===t,s=t=>d.value===t;e.watch(d,t=>{if(a.collapsible&&t&&!u.value.has(t)){const l=new Set(u.value);l.add(t),u.value=l}});const v=e.ref(0),m=e.computed(()=>{const t=[],l=o=>{if(o)for(const i of o)t.push(i.id),i.children&&l(i.children)};return l(a.toc?.links),t});return e.watch(h,t=>{if(!t)return;const l=m.value.indexOf(t);if(l!==-1){const o=m.value.length;o>0&&(v.value=(l+1)/o*100)}},{immediate:!0}),e.onMounted(()=>{a.collapsible&&d.value&&E(d.value),k()}),e.onUnmounted(()=>{y()}),(t,l)=>(e.openBlock(),e.createElementBlock("nav",b,[n.toc?.links?.length?(e.openBlock(),e.createElementBlock("div",w,[e.createElementVNode("div",{class:"tv-toc-progress-bar",style:e.normalizeStyle({height:`${v.value}%`})},null,4)])):e.createCommentVNode("",!0),n.toc?.title?(e.openBlock(),e.createElementBlock("h3",C,e.toDisplayString(n.toc.title),1)):e.createCommentVNode("",!0),e.createElementVNode("ul",I,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(n.toc?.links,o=>(e.openBlock(),e.createElementBlock("li",{key:o.id,class:"tv-toc-item"},[e.createElementVNode("div",T,[e.createElementVNode("a",{href:`#${o.id}`,class:e.normalizeClass(["tv-toc-link",{[a.activeClass]:c(o.id),"parent-active":s(o.id),"tv-toc-marker":n.marker&&c(o.id)}]),onClick:e.withModifiers(i=>r(o.id),["prevent"])},e.toDisplayString(o.text),11,S),n.collapsible&&o.children&&o.children.length?(e.openBlock(),e.createElementBlock("button",{key:0,class:e.normalizeClass(["tv-toc-toggle",{"is-expanded":g(o.id)}]),onClick:e.withModifiers(i=>E(o.id),["stop"]),"aria-label":"Toggle section"},[...l[0]||(l[0]=[e.createElementVNode("svg",{xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},[e.createElementVNode("polyline",{points:"6 9 12 15 18 9"})],-1)])],10,x)):e.createCommentVNode("",!0)]),o.children?(e.openBlock(),e.createElementBlock("div",{key:0,class:e.normalizeClass(["tv-toc-sublist-wrapper",{"is-collapsed":n.collapsible&&!g(o.id)}]),style:e.normalizeStyle(n.collapsible?{"--content-height":g(o.id)?"1000px":"0px"}:{})},[e.createElementVNode("ul",V,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(o.children,i=>(e.openBlock(),e.createElementBlock("li",{key:i.id,class:"tv-toc-subitem"},[e.createElementVNode("a",{href:`#${i.id}`,class:e.normalizeClass(["tv-toc-sublink",{[a.activeClass]:c(i.id),"tv-toc-marker":n.marker&&c(i.id)}]),onClick:e.withModifiers(M=>r(i.id),["prevent"])},e.toDisplayString(i.text),11,N)]))),128))])],6)):e.createCommentVNode("",!0)]))),128))])]))}},f=O;f.install=n=>{n.component("TvToc",f)};const $={install:f.install};exports.TvToc=f;exports.TvTocPlugin=$;exports.default=f;
|
package/dist/tv-toc.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.tv-toc{padding:1rem;background-color:#b9c4df;border-radius:8px;color:#000b14;min-width:200px}@media(prefers-color-scheme:dark){.tv-toc{background-color:#0e131f;color:#f4faff}}.tv-toc .tv-toc-title{font-size:1.2rem;font-weight:700;margin-bottom:.5rem}.tv-toc .tv-toc-list{list-style:none;padding:0;margin:0}.tv-toc .tv-toc-item{margin-bottom:.5rem}.tv-toc .tv-toc-link{text-decoration:none;color:inherit;font-weight:500;transition:color .2s}.tv-toc .tv-toc-link:
|
|
1
|
+
.tv-toc{position:relative;padding:1rem;background-color:#b9c4df;border-radius:8px;color:#000b14;min-width:200px;overflow:hidden;--toc-active-rgb: 239, 35, 60;--toc-active-color: #EF233C}@media(prefers-color-scheme:dark){.tv-toc{background-color:#0e131f;color:#f4faff;--toc-active-rgb: 239, 35, 60;--toc-active-color: #EF233C}}.tv-toc .tv-toc-progress-container{position:absolute;left:0;top:0;bottom:0;width:4px;background-color:#ef233c1a;z-index:1}@media(prefers-color-scheme:dark){.tv-toc .tv-toc-progress-container{background-color:#ef233c1a}}.tv-toc .tv-toc-progress-bar{width:100%;background:linear-gradient(to bottom,#ef233c,#f68290);transition:height .1s linear;border-radius:0 0 4px 4px}@media(prefers-color-scheme:dark){.tv-toc .tv-toc-progress-bar{background:linear-gradient(to bottom,#ef233c,#f68290)}}.tv-toc .tv-toc-title{font-size:1.2rem;font-weight:700;margin-bottom:.5rem}.tv-toc .tv-toc-list{list-style:none;padding:0;margin:0}.tv-toc .tv-toc-item{margin-bottom:.5rem;position:relative;opacity:0;animation:fadeSlideIn .5s ease forwards}.tv-toc .tv-toc-item:nth-child(1){animation-delay:.05s}.tv-toc .tv-toc-item:nth-child(2){animation-delay:.1s}.tv-toc .tv-toc-item:nth-child(3){animation-delay:.15s}.tv-toc .tv-toc-item:nth-child(4){animation-delay:.2s}.tv-toc .tv-toc-item:nth-child(5){animation-delay:.25s}.tv-toc .tv-toc-item:nth-child(6){animation-delay:.3s}.tv-toc .tv-toc-item:nth-child(7){animation-delay:.35s}.tv-toc .tv-toc-item:nth-child(8){animation-delay:.4s}.tv-toc .tv-toc-item:nth-child(9){animation-delay:.45s}.tv-toc .tv-toc-item:nth-child(10){animation-delay:.5s}.tv-toc .tv-toc-item:nth-child(11){animation-delay:.55s}.tv-toc .tv-toc-item:nth-child(12){animation-delay:.6s}.tv-toc .tv-toc-item:nth-child(13){animation-delay:.65s}.tv-toc .tv-toc-item:nth-child(14){animation-delay:.7s}.tv-toc .tv-toc-item:nth-child(15){animation-delay:.75s}.tv-toc .tv-toc-item:nth-child(16){animation-delay:.8s}.tv-toc .tv-toc-item:nth-child(17){animation-delay:.85s}.tv-toc .tv-toc-item:nth-child(18){animation-delay:.9s}.tv-toc .tv-toc-item:nth-child(19){animation-delay:.95s}.tv-toc .tv-toc-item:nth-child(20){animation-delay:1s}.tv-toc .tv-toc-item .tv-toc-item-content{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.tv-toc .tv-toc-item .tv-toc-item-content .tv-toc-link{flex:1}.tv-toc .tv-toc-item .tv-toc-toggle{background:transparent;border:none;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;color:inherit;opacity:.6;transition:all .2s ease;border-radius:4px;margin-right:4px}.tv-toc .tv-toc-item .tv-toc-toggle:hover{background-color:#ef233c1a;opacity:1}.tv-toc .tv-toc-item .tv-toc-toggle svg{transition:transform .3s ease}.tv-toc .tv-toc-item .tv-toc-toggle.is-expanded svg{transform:rotate(180deg)}.tv-toc .tv-toc-item:hover .tv-toc-link,.tv-toc .tv-toc-item:hover .tv-toc-sublink{background-color:#ef233c0d}@media(prefers-color-scheme:dark){.tv-toc .tv-toc-item:hover .tv-toc-link,.tv-toc .tv-toc-item:hover .tv-toc-sublink{background-color:#ef233c0d}.tv-toc .tv-toc-item .tv-toc-toggle:hover{background-color:#ef233c1a}}.tv-toc .tv-toc-link{display:block;text-decoration:none;color:inherit;font-weight:500;padding:.5rem .75rem;border-radius:4px;transition:all .3s ease;position:relative}.tv-toc .tv-toc-link:hover{color:#ef233c}.tv-toc .tv-toc-link.active{color:#ef233c;font-weight:700;background-color:#ef233c1a;padding-left:1rem;position:relative;animation:toc-pulse 2s infinite}.tv-toc .tv-toc-link.active:before{content:"";position:absolute;left:0;top:0;bottom:0;width:4px;background:linear-gradient(180deg,var(--toc-active-color),rgba(var(--toc-active-rgb),.2),var(--toc-active-color));background-size:100% 200%;animation:border-flow 2s linear infinite;border-radius:4px 0 0 4px}.tv-toc .tv-toc-link.parent-active{color:#ef233c;font-weight:500;border-left:2px solid rgba(239,35,60,.5);padding-left:calc(1rem - 2px)}@media(prefers-color-scheme:dark){.tv-toc .tv-toc-link:hover{color:#ef233c}.tv-toc .tv-toc-link.active{color:#ef233c;background-color:#ef233c1a}.tv-toc .tv-toc-link.parent-active{color:#ef233c;border-left-color:#ef233c80}}.tv-toc .tv-toc-sublist-wrapper{overflow:hidden;transition:opacity .3s ease,max-height .3s ease;max-height:1000px;opacity:1}.tv-toc .tv-toc-sublist-wrapper.is-collapsed{max-height:0;opacity:0;margin-bottom:0}.tv-toc .tv-toc-sublist{list-style:none;padding-left:1rem;margin-top:.25rem;margin-bottom:.25rem;border-left:2px solid rgba(0,11,20,.1)}@media(prefers-color-scheme:dark){.tv-toc .tv-toc-sublist{border-left-color:#f4faff1a}}.tv-toc .tv-toc-subitem{margin-bottom:.25rem;position:relative;opacity:0;animation:fadeSlideIn .5s ease forwards}.tv-toc .tv-toc-subitem:nth-child(1){animation-delay:.35s}.tv-toc .tv-toc-subitem:nth-child(2){animation-delay:.4s}.tv-toc .tv-toc-subitem:nth-child(3){animation-delay:.45s}.tv-toc .tv-toc-subitem:nth-child(4){animation-delay:.5s}.tv-toc .tv-toc-subitem:nth-child(5){animation-delay:.55s}.tv-toc .tv-toc-subitem:nth-child(6){animation-delay:.6s}.tv-toc .tv-toc-subitem:nth-child(7){animation-delay:.65s}.tv-toc .tv-toc-subitem:nth-child(8){animation-delay:.7s}.tv-toc .tv-toc-subitem:nth-child(9){animation-delay:.75s}.tv-toc .tv-toc-subitem:nth-child(10){animation-delay:.8s}.tv-toc .tv-toc-sublink{display:block;text-decoration:none;color:inherit;font-size:.9rem;opacity:.8;padding:.4rem .75rem;border-radius:4px;transition:all .3s ease;position:relative}.tv-toc .tv-toc-sublink:hover{opacity:1;color:#ef233c}.tv-toc .tv-toc-sublink.active{opacity:1;color:#ef233c;font-weight:700;background-color:#ef233c1a;padding-left:1rem;position:relative;animation:toc-pulse 2s infinite}.tv-toc .tv-toc-sublink.active:before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:linear-gradient(180deg,var(--toc-active-color),rgba(var(--toc-active-rgb),.2),var(--toc-active-color));background-size:100% 200%;animation:border-flow 2s linear infinite;border-radius:4px 0 0 4px}@media(prefers-color-scheme:dark){.tv-toc .tv-toc-sublink:hover{color:#ef233c}.tv-toc .tv-toc-sublink.active{color:#ef233c;background-color:#ef233c1a}}.light-mode .tv-toc{background-color:#b9c4df;color:#000b14;--toc-active-rgb: 239, 35, 60;--toc-active-color: #EF233C}.light-mode .tv-toc .tv-toc-link:hover,.light-mode .tv-toc .tv-toc-link.active,.light-mode .tv-toc .tv-toc-link.parent-active{color:#ef233c}.light-mode .tv-toc .tv-toc-link.parent-active{border-left-color:#ef233c80}.light-mode .tv-toc .tv-toc-sublist{border-left-color:#000b141a}.light-mode .tv-toc .tv-toc-sublink:hover,.light-mode .tv-toc .tv-toc-sublink.active{color:#ef233c}.tv-toc-marker{border-left:2px solid var(--tv-toc-active-color, #3b82f6);padding-left:8px;transition:all .2s ease}@keyframes fadeSlideIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes toc-pulse{0%{box-shadow:0 0 rgba(var(--toc-active-rgb),.4)}70%{box-shadow:0 0 0 6px rgba(var(--toc-active-rgb),0)}to{box-shadow:0 0 rgba(var(--toc-active-rgb),0)}}@keyframes border-flow{0%{background-position:0 0}to{background-position:0 200%}}
|
package/dist/tv-toc.es.js
CHANGED
|
@@ -1,65 +1,186 @@
|
|
|
1
|
-
import { createElementBlock as
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
1
|
+
import { ref as w, watch as $, computed as _, onMounted as j, onUnmounted as A, createElementBlock as d, openBlock as u, createCommentVNode as y, createElementVNode as h, normalizeStyle as k, toDisplayString as O, Fragment as B, renderList as P, withModifiers as S, normalizeClass as I } from "vue";
|
|
2
|
+
const M = (l = [], i = {}) => {
|
|
3
|
+
const g = w(null), m = w(null);
|
|
4
|
+
let a = null;
|
|
5
|
+
const x = (c) => `#${c}`, C = (c) => {
|
|
6
|
+
const s = document.getElementById(c);
|
|
7
|
+
s && (s.scrollIntoView({ behavior: "smooth" }), history.pushState(null, null, `#${c}`));
|
|
8
|
+
}, v = (c) => {
|
|
9
|
+
const s = [];
|
|
10
|
+
return c.forEach((n) => {
|
|
11
|
+
s.push({ id: n.id, parentId: null }), n.children && n.children.forEach((f) => {
|
|
12
|
+
s.push({ id: f.id, parentId: n.id });
|
|
13
|
+
});
|
|
14
|
+
}), s;
|
|
15
|
+
};
|
|
16
|
+
return {
|
|
17
|
+
formatId: x,
|
|
18
|
+
scrollToId: C,
|
|
19
|
+
activeId: g,
|
|
20
|
+
activeParentId: m,
|
|
21
|
+
setupObserver: () => {
|
|
22
|
+
if (typeof window > "u" || !l.length) return;
|
|
23
|
+
const c = v(l), s = c.map(({ id: n }) => document.getElementById(n)).filter((n) => n !== null);
|
|
24
|
+
s.length && (a = new IntersectionObserver(
|
|
25
|
+
(n) => {
|
|
26
|
+
n.forEach((f) => {
|
|
27
|
+
if (f.isIntersecting) {
|
|
28
|
+
const p = f.target.id, e = c.find((o) => o.id === p);
|
|
29
|
+
e && (g.value = p, m.value = e.parentId, history.replaceState(null, null, `#${p}`));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
rootMargin: "-20% 0px -70% 0px",
|
|
35
|
+
threshold: 0,
|
|
36
|
+
...i
|
|
37
|
+
}
|
|
38
|
+
), s.forEach((n) => a.observe(n)));
|
|
39
|
+
},
|
|
40
|
+
cleanup: () => {
|
|
41
|
+
a && (a.disconnect(), a = null);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}, V = { class: "tv-toc" }, z = {
|
|
9
45
|
key: 0,
|
|
46
|
+
class: "tv-toc-progress-container"
|
|
47
|
+
}, N = {
|
|
48
|
+
key: 1,
|
|
10
49
|
class: "tv-toc-title"
|
|
11
|
-
},
|
|
12
|
-
key: 0,
|
|
13
|
-
class: "tv-toc-sublist"
|
|
14
|
-
}, k = ["href", "onClick"], C = {
|
|
50
|
+
}, q = { class: "tv-toc-list" }, D = { class: "tv-toc-item-content" }, F = ["href", "onClick"], U = ["onClick"], G = { class: "tv-toc-sublist" }, H = ["href", "onClick"], J = {
|
|
15
51
|
__name: "TvToc",
|
|
16
52
|
props: {
|
|
17
53
|
toc: {
|
|
18
54
|
type: Object,
|
|
19
55
|
required: !0
|
|
56
|
+
},
|
|
57
|
+
marker: {
|
|
58
|
+
type: Boolean,
|
|
59
|
+
default: !1
|
|
60
|
+
},
|
|
61
|
+
activeClass: {
|
|
62
|
+
type: String,
|
|
63
|
+
default: "active"
|
|
64
|
+
},
|
|
65
|
+
observerOptions: {
|
|
66
|
+
type: Object,
|
|
67
|
+
default: () => ({})
|
|
68
|
+
},
|
|
69
|
+
collapsible: {
|
|
70
|
+
type: Boolean,
|
|
71
|
+
default: !1
|
|
20
72
|
}
|
|
21
73
|
},
|
|
22
|
-
setup(
|
|
23
|
-
const { scrollToId:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
74
|
+
setup(l) {
|
|
75
|
+
const i = l, { scrollToId: g, activeId: m, activeParentId: a, setupObserver: x, cleanup: C } = M(i.toc?.links || [], i.observerOptions), v = w(/* @__PURE__ */ new Set()), T = (e) => {
|
|
76
|
+
const o = new Set(v.value);
|
|
77
|
+
o.has(e) ? o.delete(e) : o.add(e), v.value = o;
|
|
78
|
+
}, b = (e) => !i.collapsible || v.value.has(e), c = (e) => {
|
|
79
|
+
g(e);
|
|
80
|
+
}, s = (e) => m.value === e, n = (e) => a.value === e;
|
|
81
|
+
$(a, (e) => {
|
|
82
|
+
if (i.collapsible && e && !v.value.has(e)) {
|
|
83
|
+
const o = new Set(v.value);
|
|
84
|
+
o.add(e), v.value = o;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
const f = w(0), p = _(() => {
|
|
88
|
+
const e = [], o = (t) => {
|
|
89
|
+
if (t)
|
|
90
|
+
for (const r of t)
|
|
91
|
+
e.push(r.id), r.children && o(r.children);
|
|
92
|
+
};
|
|
93
|
+
return o(i.toc?.links), e;
|
|
94
|
+
});
|
|
95
|
+
return $(m, (e) => {
|
|
96
|
+
if (!e) return;
|
|
97
|
+
const o = p.value.indexOf(e);
|
|
98
|
+
if (o !== -1) {
|
|
99
|
+
const t = p.value.length;
|
|
100
|
+
t > 0 && (f.value = (o + 1) / t * 100);
|
|
101
|
+
}
|
|
102
|
+
}, { immediate: !0 }), j(() => {
|
|
103
|
+
i.collapsible && a.value && T(a.value), x();
|
|
104
|
+
}), A(() => {
|
|
105
|
+
C();
|
|
106
|
+
}), (e, o) => (u(), d("nav", V, [
|
|
107
|
+
l.toc?.links?.length ? (u(), d("div", z, [
|
|
108
|
+
h("div", {
|
|
109
|
+
class: "tv-toc-progress-bar",
|
|
110
|
+
style: k({ height: `${f.value}%` })
|
|
111
|
+
}, null, 4)
|
|
112
|
+
])) : y("", !0),
|
|
113
|
+
l.toc?.title ? (u(), d("h3", N, O(l.toc.title), 1)) : y("", !0),
|
|
114
|
+
h("ul", q, [
|
|
115
|
+
(u(!0), d(B, null, P(l.toc?.links, (t) => (u(), d("li", {
|
|
116
|
+
key: t.id,
|
|
31
117
|
class: "tv-toc-item"
|
|
32
118
|
}, [
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
119
|
+
h("div", D, [
|
|
120
|
+
h("a", {
|
|
121
|
+
href: `#${t.id}`,
|
|
122
|
+
class: I(["tv-toc-link", {
|
|
123
|
+
[i.activeClass]: s(t.id),
|
|
124
|
+
"parent-active": n(t.id),
|
|
125
|
+
"tv-toc-marker": l.marker && s(t.id)
|
|
126
|
+
}]),
|
|
127
|
+
onClick: S((r) => c(t.id), ["prevent"])
|
|
128
|
+
}, O(t.text), 11, F),
|
|
129
|
+
l.collapsible && t.children && t.children.length ? (u(), d("button", {
|
|
130
|
+
key: 0,
|
|
131
|
+
class: I(["tv-toc-toggle", { "is-expanded": b(t.id) }]),
|
|
132
|
+
onClick: S((r) => T(t.id), ["stop"]),
|
|
133
|
+
"aria-label": "Toggle section"
|
|
134
|
+
}, [...o[0] || (o[0] = [
|
|
135
|
+
h("svg", {
|
|
136
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
137
|
+
width: "16",
|
|
138
|
+
height: "16",
|
|
139
|
+
viewBox: "0 0 24 24",
|
|
140
|
+
fill: "none",
|
|
141
|
+
stroke: "currentColor",
|
|
142
|
+
"stroke-width": "2",
|
|
143
|
+
"stroke-linecap": "round",
|
|
144
|
+
"stroke-linejoin": "round"
|
|
145
|
+
}, [
|
|
146
|
+
h("polyline", { points: "6 9 12 15 18 9" })
|
|
147
|
+
], -1)
|
|
148
|
+
])], 10, U)) : y("", !0)
|
|
149
|
+
]),
|
|
150
|
+
t.children ? (u(), d("div", {
|
|
151
|
+
key: 0,
|
|
152
|
+
class: I(["tv-toc-sublist-wrapper", { "is-collapsed": l.collapsible && !b(t.id) }]),
|
|
153
|
+
style: k(l.collapsible ? { "--content-height": b(t.id) ? "1000px" : "0px" } : {})
|
|
154
|
+
}, [
|
|
155
|
+
h("ul", G, [
|
|
156
|
+
(u(!0), d(B, null, P(t.children, (r) => (u(), d("li", {
|
|
157
|
+
key: r.id,
|
|
158
|
+
class: "tv-toc-subitem"
|
|
159
|
+
}, [
|
|
160
|
+
h("a", {
|
|
161
|
+
href: `#${r.id}`,
|
|
162
|
+
class: I(["tv-toc-sublink", {
|
|
163
|
+
[i.activeClass]: s(r.id),
|
|
164
|
+
"tv-toc-marker": l.marker && s(r.id)
|
|
165
|
+
}]),
|
|
166
|
+
onClick: S((K) => c(r.id), ["prevent"])
|
|
167
|
+
}, O(r.text), 11, H)
|
|
168
|
+
]))), 128))
|
|
169
|
+
])
|
|
170
|
+
], 6)) : y("", !0)
|
|
50
171
|
]))), 128))
|
|
51
172
|
])
|
|
52
173
|
]));
|
|
53
174
|
}
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
|
|
175
|
+
}, E = J;
|
|
176
|
+
E.install = (l) => {
|
|
177
|
+
l.component("TvToc", E);
|
|
57
178
|
};
|
|
58
|
-
const
|
|
59
|
-
install:
|
|
179
|
+
const R = {
|
|
180
|
+
install: E.install
|
|
60
181
|
};
|
|
61
182
|
export {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
183
|
+
E as TvToc,
|
|
184
|
+
R as TvTocPlugin,
|
|
185
|
+
E as default
|
|
65
186
|
};
|
package/package.json
CHANGED
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
"author": "Cristhian Daza",
|
|
5
5
|
"description": "A Vue 3 component to generate a table of contents (TOC) for your articles or documentation, enhancing navigation and user experience.",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"version": "1.0
|
|
7
|
+
"version": "1.1.0",
|
|
8
8
|
"type": "module",
|
|
9
|
+
"homepage": "https://ui.todovue.blog/toc",
|
|
9
10
|
"repository": {
|
|
10
11
|
"type": "git",
|
|
11
|
-
"url": "https://github.com/TODOvue/tv-toc.git"
|
|
12
|
+
"url": "git+https://github.com/TODOvue/tv-toc.git"
|
|
12
13
|
},
|
|
13
14
|
"bugs": {
|
|
14
15
|
"url": "https://github.com/TODOvue/tv-toc/issues"
|
|
@@ -56,13 +57,13 @@
|
|
|
56
57
|
"build:demo": "cp README.md public/ && cp CHANGELOG.md public/ && VITE_BUILD_TARGET=demo vite build"
|
|
57
58
|
},
|
|
58
59
|
"peerDependencies": {
|
|
59
|
-
"vue": "^3.
|
|
60
|
+
"vue": "^3.5.26"
|
|
60
61
|
},
|
|
61
62
|
"devDependencies": {
|
|
62
|
-
"@todovue/tv-demo": "^1.
|
|
63
|
-
"@vitejs/plugin-vue": "^6.0.
|
|
64
|
-
"sass": "^1.
|
|
65
|
-
"vite": "^7.
|
|
66
|
-
"vite-plugin-dts": "^4.
|
|
63
|
+
"@todovue/tv-demo": "^1.4.4",
|
|
64
|
+
"@vitejs/plugin-vue": "^6.0.3",
|
|
65
|
+
"sass": "^1.97.2",
|
|
66
|
+
"vite": "^7.3.1",
|
|
67
|
+
"vite-plugin-dts": "^4.5.4"
|
|
67
68
|
}
|
|
68
69
|
}
|