@todovue/tv-toc 1.0.2 → 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/README.md +13 -24
- package/dist/tv-toc.cjs.js +1 -1
- package/dist/tv-toc.css +1 -1
- package/dist/tv-toc.es.js +153 -74
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -16,13 +16,12 @@ A lightweight Vue 3 component to render a Table of Contents (TOC) for your artic
|
|
|
16
16
|
|
|
17
17
|
> Demo: https://ui.todovue.blog/toc
|
|
18
18
|
|
|
19
|
-
---
|
|
20
19
|
## Table of Contents
|
|
21
20
|
- [Features](#features)
|
|
22
21
|
- [Installation](#installation)
|
|
23
22
|
- [Usage of Styles](#usage-of-styles)
|
|
24
23
|
- [Quick Start (SPA)](#quick-start-spa)
|
|
25
|
-
- [Nuxt
|
|
24
|
+
- [Nuxt 4 / SSR Usage](#nuxt-4--ssr-usage)
|
|
26
25
|
- [Component Registration Options](#component-registration-options)
|
|
27
26
|
- [Props](#props)
|
|
28
27
|
- [Composable: useToc](#composable-usetoc)
|
|
@@ -33,7 +32,6 @@ A lightweight Vue 3 component to render a Table of Contents (TOC) for your artic
|
|
|
33
32
|
- [Contributing](#contributing)
|
|
34
33
|
- [License](#license)
|
|
35
34
|
|
|
36
|
-
---
|
|
37
35
|
## Features
|
|
38
36
|
- Simple and focused Table of Contents (TOC) component for Vue 3.
|
|
39
37
|
- Supports nested sections via children links.
|
|
@@ -42,7 +40,6 @@ A lightweight Vue 3 component to render a Table of Contents (TOC) for your artic
|
|
|
42
40
|
- Works in SPA (Vite, Vue CLI) and Nuxt 3 (with client-side rendering constraints).
|
|
43
41
|
- Ships with minimal, customizable styles.
|
|
44
42
|
|
|
45
|
-
---
|
|
46
43
|
## Installation
|
|
47
44
|
Using npm:
|
|
48
45
|
```bash
|
|
@@ -57,7 +54,6 @@ Using pnpm:
|
|
|
57
54
|
pnpm add @todovue/tv-toc
|
|
58
55
|
```
|
|
59
56
|
|
|
60
|
-
---
|
|
61
57
|
## Usage of Styles
|
|
62
58
|
|
|
63
59
|
### Vue/Vite (SPA)
|
|
@@ -86,7 +82,6 @@ export default defineNuxtConfig({
|
|
|
86
82
|
})
|
|
87
83
|
```
|
|
88
84
|
|
|
89
|
-
---
|
|
90
85
|
## Quick Start (SPA)
|
|
91
86
|
Global registration (main.js / main.ts):
|
|
92
87
|
```js
|
|
@@ -139,8 +134,7 @@ const toc = {
|
|
|
139
134
|
</template>
|
|
140
135
|
```
|
|
141
136
|
|
|
142
|
-
|
|
143
|
-
## Nuxt 3 / SSR Usage
|
|
137
|
+
## Nuxt 4 / SSR Usage
|
|
144
138
|
Create a plugin file: `plugins/tv-toc.client.ts` (client-only because it uses `document` and `history` under the hood when scrolling):
|
|
145
139
|
```ts
|
|
146
140
|
import { defineNuxtPlugin } from '#app'
|
|
@@ -169,7 +163,6 @@ import { TvToc } from '@todovue/tv-toc'
|
|
|
169
163
|
</template>
|
|
170
164
|
```
|
|
171
165
|
|
|
172
|
-
---
|
|
173
166
|
## Component Registration Options
|
|
174
167
|
| Approach | When to use |
|
|
175
168
|
|-----------------------------------------------|-----------------------------------|
|
|
@@ -177,11 +170,14 @@ import { TvToc } from '@todovue/tv-toc'
|
|
|
177
170
|
| Local named import `{ TvToc }` | Isolated/code-split contexts |
|
|
178
171
|
| Direct default import `import TvToc from ...` | Single use or manual registration |
|
|
179
172
|
|
|
180
|
-
---
|
|
181
173
|
## Props
|
|
182
|
-
| Name
|
|
183
|
-
|
|
184
|
-
| 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` |
|
|
185
181
|
|
|
186
182
|
### `toc` shape
|
|
187
183
|
```ts
|
|
@@ -200,16 +196,17 @@ type Toc = {
|
|
|
200
196
|
- `links`: Array of top-level sections.
|
|
201
197
|
- `id`: Must match the `id` attribute of the target heading in your content.
|
|
202
198
|
- `text`: Label shown in the TOC.
|
|
203
|
-
- `children`: Optional array of
|
|
199
|
+
- `children`: Optional array of subsections, rendered as nested list.
|
|
204
200
|
|
|
205
|
-
---
|
|
206
201
|
## Composable: `useToc`
|
|
207
202
|
This composable is used internally by `TvToc` but can also be imported directly if needed.
|
|
208
203
|
|
|
209
204
|
```ts
|
|
210
205
|
import { useToc } from '@todovue/tv-toc'
|
|
211
206
|
|
|
212
|
-
const { formatId, scrollToId } = useToc(
|
|
207
|
+
const { formatId, scrollToId } = useToc(links, {
|
|
208
|
+
rootMargin: '0px 0px -50% 0px'
|
|
209
|
+
})
|
|
213
210
|
```
|
|
214
211
|
|
|
215
212
|
### API
|
|
@@ -220,7 +217,6 @@ const { formatId, scrollToId } = useToc()
|
|
|
220
217
|
|
|
221
218
|
> Note: `scrollToId` accesses `document` and `history`, so it should run only in the browser (e.g. in event handlers or inside `onMounted`).
|
|
222
219
|
|
|
223
|
-
---
|
|
224
220
|
## Customization (Styles)
|
|
225
221
|
The component ships with minimal default styles, exposed through the built CSS file and scoped CSS classes.
|
|
226
222
|
|
|
@@ -254,13 +250,11 @@ You can override these styles in your own global stylesheet:
|
|
|
254
250
|
|
|
255
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`).
|
|
256
252
|
|
|
257
|
-
---
|
|
258
253
|
## SSR Notes
|
|
259
254
|
- The component can be rendered on the server (template is static), but scrolling behavior uses browser APIs.
|
|
260
255
|
- `scrollToId` uses `document.getElementById` and `history.pushState`; these are only invoked in event handlers on the client.
|
|
261
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.
|
|
262
257
|
|
|
263
|
-
---
|
|
264
258
|
## Examples
|
|
265
259
|
This repository includes a small demo application built with Vite.
|
|
266
260
|
|
|
@@ -269,7 +263,6 @@ This repository includes a small demo application built with Vite.
|
|
|
269
263
|
|
|
270
264
|
To run the demo locally, see the [Development](#development) section.
|
|
271
265
|
|
|
272
|
-
---
|
|
273
266
|
## Development
|
|
274
267
|
```bash
|
|
275
268
|
git clone https://github.com/TODOvue/tv-toc.git
|
|
@@ -285,15 +278,11 @@ To build the standalone demo used for documentation:
|
|
|
285
278
|
npm run build:demo
|
|
286
279
|
```
|
|
287
280
|
|
|
288
|
-
---
|
|
289
281
|
## Contributing
|
|
290
282
|
PRs and issues are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
|
291
283
|
|
|
292
|
-
---
|
|
293
284
|
## License
|
|
294
285
|
MIT © TODOvue
|
|
295
286
|
|
|
296
|
-
---
|
|
297
287
|
### Attributions
|
|
298
288
|
Crafted for the TODOvue component ecosystem
|
|
299
|
-
|
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;position:relative}.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-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;
|
|
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,107 +1,186 @@
|
|
|
1
|
-
import { ref as
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
let
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
},
|
|
9
|
-
const
|
|
10
|
-
return
|
|
11
|
-
|
|
12
|
-
|
|
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
13
|
});
|
|
14
|
-
}),
|
|
14
|
+
}), s;
|
|
15
15
|
};
|
|
16
16
|
return {
|
|
17
|
-
formatId:
|
|
18
|
-
scrollToId:
|
|
19
|
-
activeId:
|
|
20
|
-
activeParentId:
|
|
17
|
+
formatId: x,
|
|
18
|
+
scrollToId: C,
|
|
19
|
+
activeId: g,
|
|
20
|
+
activeParentId: m,
|
|
21
21
|
setupObserver: () => {
|
|
22
|
-
if (typeof window > "u" || !
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
(
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
const
|
|
29
|
-
|
|
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
30
|
}
|
|
31
31
|
});
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
rootMargin: "-20% 0px -70% 0px",
|
|
35
|
-
threshold: 0
|
|
35
|
+
threshold: 0,
|
|
36
|
+
...i
|
|
36
37
|
}
|
|
37
|
-
),
|
|
38
|
+
), s.forEach((n) => a.observe(n)));
|
|
38
39
|
},
|
|
39
40
|
cleanup: () => {
|
|
40
|
-
|
|
41
|
+
a && (a.disconnect(), a = null);
|
|
41
42
|
}
|
|
42
43
|
};
|
|
43
|
-
},
|
|
44
|
+
}, V = { class: "tv-toc" }, z = {
|
|
44
45
|
key: 0,
|
|
46
|
+
class: "tv-toc-progress-container"
|
|
47
|
+
}, N = {
|
|
48
|
+
key: 1,
|
|
45
49
|
class: "tv-toc-title"
|
|
46
|
-
},
|
|
47
|
-
key: 0,
|
|
48
|
-
class: "tv-toc-sublist"
|
|
49
|
-
}, N = ["href", "onClick"], j = {
|
|
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 = {
|
|
50
51
|
__name: "TvToc",
|
|
51
52
|
props: {
|
|
52
53
|
toc: {
|
|
53
54
|
type: Object,
|
|
54
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
|
|
55
72
|
}
|
|
56
73
|
},
|
|
57
|
-
setup(
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}),
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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,
|
|
70
117
|
class: "tv-toc-item"
|
|
71
118
|
}, [
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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)
|
|
92
171
|
]))), 128))
|
|
93
172
|
])
|
|
94
173
|
]));
|
|
95
174
|
}
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
|
|
175
|
+
}, E = J;
|
|
176
|
+
E.install = (l) => {
|
|
177
|
+
l.component("TvToc", E);
|
|
99
178
|
};
|
|
100
|
-
const
|
|
101
|
-
install:
|
|
179
|
+
const R = {
|
|
180
|
+
install: E.install
|
|
102
181
|
};
|
|
103
182
|
export {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
183
|
+
E as TvToc,
|
|
184
|
+
R as TvTocPlugin,
|
|
185
|
+
E as default
|
|
107
186
|
};
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
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
9
|
"homepage": "https://ui.todovue.blog/toc",
|
|
10
10
|
"repository": {
|
|
@@ -60,10 +60,10 @@
|
|
|
60
60
|
"vue": "^3.5.26"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@todovue/tv-demo": "^1.
|
|
63
|
+
"@todovue/tv-demo": "^1.4.4",
|
|
64
64
|
"@vitejs/plugin-vue": "^6.0.3",
|
|
65
|
-
"sass": "^1.97.
|
|
66
|
-
"vite": "^7.3.
|
|
65
|
+
"sass": "^1.97.2",
|
|
66
|
+
"vite": "^7.3.1",
|
|
67
67
|
"vite-plugin-dts": "^4.5.4"
|
|
68
68
|
}
|
|
69
69
|
}
|