vanilla-jui 1.7.6 → 1.7.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vanilla-jui",
3
- "version": "1.7.6",
3
+ "version": "1.7.8",
4
4
  "private": false,
5
5
  "description": "A lightweight, reactive, and fully customizable vanilla JS UI library.",
6
6
  "keywords": [
@@ -1,35 +1,44 @@
1
1
  import {
2
2
  For,
3
+ Show,
3
4
  createDeepStore,
5
+ createEffect,
4
6
  createMemo,
5
7
  flushSync,
8
+ insert,
6
9
  jsx,
10
+ untrack,
7
11
  } from 'vanilla-signal';
8
12
 
9
13
  import {
10
14
  type FunctionalComponent,
11
15
  defineComponent,
12
16
  } from '../core/component.ts';
13
- import {
14
- type DOMReference,
15
- all,
16
- joinClasses,
17
- requireContainer,
18
- } from '../utilities/dom.ts';
17
+ import { type DOMReference, all, requireContainer } from '../utilities/dom.ts';
19
18
  import { createEventManager } from '../utilities/events.ts';
20
- import { randomId } from '../utilities/id.ts';
21
19
  import { type ConfigSchema, resolveConfig } from '../utilities/config.ts';
20
+ import { translate } from '../utilities/locale.ts';
22
21
 
23
22
  interface TocClassNames {
24
23
  toc: string;
24
+ title: string;
25
25
  list: string;
26
26
  link: string;
27
- active: string;
28
- levelPrefix: string;
27
+ indicator: string;
29
28
  }
30
29
 
31
30
  type TocClassNameConfig = Partial<TocClassNames>;
32
31
 
32
+ interface TocIndicatorConfig {
33
+ width: string;
34
+ heightRatio: number;
35
+ radius: string;
36
+ left: string;
37
+ color: string;
38
+ }
39
+
40
+ type TocIndicatorConfigInput = Partial<TocIndicatorConfig>;
41
+
33
42
  export interface TocItem {
34
43
  id: string;
35
44
  text: string;
@@ -41,11 +50,21 @@ export interface TocCurrent {
41
50
  item: TocItem | null;
42
51
  }
43
52
 
53
+ export interface TocIndicator {
54
+ visible: boolean;
55
+ top: number;
56
+ height: number;
57
+ duration: number;
58
+ timingFunction: string;
59
+ }
60
+
44
61
  interface TocProps extends Record<string, unknown> {
45
62
  target?: DOMReference;
46
63
  headings?: string;
47
64
  offset?: number;
48
65
  reactive?: boolean;
66
+ title?: boolean;
67
+ indicator?: TocIndicatorConfigInput;
49
68
  className?: TocClassNameConfig;
50
69
  onChange?:
51
70
  | ((item: TocItem | null, index: number, toc: TocInstance) => void)
@@ -57,6 +76,8 @@ interface ResolvedTocProps extends Record<string, unknown> {
57
76
  headings: string;
58
77
  offset: number;
59
78
  reactive: boolean;
79
+ title: boolean;
80
+ indicator: TocIndicatorConfig;
60
81
  className: TocClassNames;
61
82
  onChange:
62
83
  | ((item: TocItem | null, index: number, toc: TocInstance) => void)
@@ -66,6 +87,7 @@ interface ResolvedTocProps extends Record<string, unknown> {
66
87
  interface TocState extends Record<string, unknown> {
67
88
  items: TocItem[];
68
89
  current: TocCurrent;
90
+ indicator: TocIndicator;
69
91
  }
70
92
 
71
93
  interface TocRuntimeExtras {
@@ -73,6 +95,8 @@ interface TocRuntimeExtras {
73
95
  frameId: number;
74
96
  refreshing: boolean;
75
97
  refreshFrameId: number;
98
+ layoutFrameId: number;
99
+ pendingScroll: TocPendingScroll | null;
76
100
  }
77
101
 
78
102
  interface TocScrollOptions {
@@ -80,6 +104,12 @@ interface TocScrollOptions {
80
104
  updateHash?: boolean;
81
105
  }
82
106
 
107
+ interface TocPendingScroll {
108
+ index: number;
109
+ top: number;
110
+ deadline: number;
111
+ }
112
+
83
113
  interface TocActions {
84
114
  activate(index: number): TocInstance;
85
115
  }
@@ -93,10 +123,18 @@ type TocInstance = FunctionalComponent<
93
123
 
94
124
  const DEFAULT_CLASS_NAMES: TocClassNames = {
95
125
  toc: 'j-toc',
126
+ title: 'toc-title',
96
127
  list: 'toc-list',
97
128
  link: 'toc-link',
98
- active: 'is-active',
99
- levelPrefix: 'is-level-',
129
+ indicator: 'toc-indicator',
130
+ };
131
+
132
+ const DEFAULT_INDICATOR_CONFIG: TocIndicatorConfig = {
133
+ width: '2px',
134
+ heightRatio: 5,
135
+ radius: '2px',
136
+ left: '0',
137
+ color: 'var(--tone-solid)',
100
138
  };
101
139
 
102
140
  const TOC_PROPS_SCHEMA = {
@@ -108,6 +146,24 @@ const TOC_PROPS_SCHEMA = {
108
146
  min: 0,
109
147
  },
110
148
  reactive: { default: false, type: 'boolean' },
149
+ title: { default: false, type: 'boolean' },
150
+ indicator: {
151
+ default: DEFAULT_INDICATOR_CONFIG,
152
+ type: 'plainObject',
153
+ merge: 'shallow',
154
+ schema: {
155
+ width: { default: DEFAULT_INDICATOR_CONFIG.width, type: 'string' },
156
+ heightRatio: {
157
+ default: DEFAULT_INDICATOR_CONFIG.heightRatio,
158
+ type: 'number',
159
+ min: 1,
160
+ max: 10,
161
+ },
162
+ radius: { default: DEFAULT_INDICATOR_CONFIG.radius, type: 'string' },
163
+ left: { default: DEFAULT_INDICATOR_CONFIG.left, type: 'string' },
164
+ color: { default: DEFAULT_INDICATOR_CONFIG.color, type: 'string' },
165
+ },
166
+ },
111
167
  className: {
112
168
  default: DEFAULT_CLASS_NAMES,
113
169
  type: 'plainObject',
@@ -117,6 +173,11 @@ const TOC_PROPS_SCHEMA = {
117
173
  } satisfies ConfigSchema<TocProps>;
118
174
 
119
175
  const ACTIVE_OFFSET_TOLERANCE = 1;
176
+ const INDICATOR_TRANSITION_MAX_MS = 1200;
177
+ const INDICATOR_TRANSITION_MIN_MS = 120;
178
+ const INDICATOR_TRANSITION_TIMING = 'ease-in-out';
179
+ const PROGRAMMATIC_SCROLL_TIMEOUT_MS = 2400;
180
+ const SCROLL_REACHED_TOLERANCE = 2;
120
181
 
121
182
  function normalizeProps(input: TocProps): ResolvedTocProps {
122
183
  const props = resolveConfig(input, TOC_PROPS_SCHEMA, 'Toc.props');
@@ -125,6 +186,8 @@ function normalizeProps(input: TocProps): ResolvedTocProps {
125
186
  headings: props.headings as string,
126
187
  offset: props.offset as number,
127
188
  reactive: props.reactive as boolean,
189
+ title: props.title as boolean,
190
+ indicator: props.indicator as TocIndicatorConfig,
128
191
  className: props.className as TocClassNames,
129
192
  onChange: props.onChange as ResolvedTocProps['onChange'],
130
193
  };
@@ -135,45 +198,260 @@ function resolveHeadingLevel(element: Element): number {
135
198
  return match ? Number(match[1]) : 1;
136
199
  }
137
200
 
138
- function normalizeHeading(element: HTMLHeadingElement, index: number): TocItem {
139
- if (!element.id) element.id = `toc-${randomId()}-${index}`;
201
+ function normalizeHeadingId(
202
+ element: HTMLHeadingElement,
203
+ index: number,
204
+ usedIds: Set<string>
205
+ ): string {
206
+ const textId = (element.textContent || '').trim().replace(/\s+/g, '-');
207
+ const base = element.id || textId || `heading-${index + 1}`;
208
+ let id = base;
209
+ let suffix = 1;
210
+ while (usedIds.has(id)) {
211
+ id = `${base}-${suffix}`;
212
+ suffix += 1;
213
+ }
214
+ element.id = id;
215
+ usedIds.add(id);
216
+ return id;
217
+ }
218
+
219
+ function normalizeHeading(
220
+ element: HTMLHeadingElement,
221
+ index: number,
222
+ usedIds: Set<string>
223
+ ): TocItem {
140
224
  return {
141
- id: element.id,
225
+ id: normalizeHeadingId(element, index, usedIds),
142
226
  text: element.textContent || '',
143
227
  level: resolveHeadingLevel(element),
144
228
  };
145
229
  }
146
230
 
231
+ function normalizeHeadings(elements: HTMLHeadingElement[]): TocItem[] {
232
+ const usedIds = new Set<string>();
233
+ return elements.map((element, index) =>
234
+ normalizeHeading(element, index, usedIds)
235
+ );
236
+ }
237
+
147
238
  export function createToc(props: TocProps = {}): TocInstance {
148
239
  const settings = normalizeProps(props);
149
240
  const state = createDeepStore({
150
241
  items: [],
151
242
  current: { index: -1, item: null },
243
+ indicator: {
244
+ visible: false,
245
+ top: 0,
246
+ height: 0,
247
+ duration: 0,
248
+ timingFunction: INDICATOR_TRANSITION_TIMING,
249
+ },
152
250
  }) as TocState;
153
251
  const runtime: TocRuntimeExtras = {
154
252
  ticking: false,
155
253
  frameId: 0,
156
254
  refreshing: false,
157
255
  refreshFrameId: 0,
256
+ layoutFrameId: 0,
257
+ pendingScroll: null,
158
258
  };
159
259
  const events = createEventManager();
160
260
  let target: Element | null = null;
161
261
  let headings: HTMLHeadingElement[] = [];
162
262
  let observer: MutationObserver | null = null;
263
+ let listElement: HTMLElement | null = null;
163
264
  let toc: TocInstance;
164
- const renderableItems = createMemo(() => state.items);
265
+ let activeLinkId: string | null = null;
266
+ let listHeight = 0;
267
+ const linkRefs = new Map<string, HTMLElement>();
268
+ const getItems = (): TocItem[] =>
269
+ Array.isArray(state.items) ? state.items : [];
270
+ const hasItems = (): boolean => getItems().length > 0;
271
+ const renderableItems = createMemo(getItems);
272
+
273
+ const setIndicator = (indicator: TocIndicator): void => {
274
+ if (
275
+ state.indicator.visible === indicator.visible &&
276
+ state.indicator.top === indicator.top &&
277
+ state.indicator.height === indicator.height &&
278
+ state.indicator.duration === indicator.duration &&
279
+ state.indicator.timingFunction === indicator.timingFunction
280
+ ) {
281
+ return;
282
+ }
283
+ flushSync(() => {
284
+ state.indicator = indicator;
285
+ });
286
+ };
287
+
288
+ const hiddenIndicator = (): TocIndicator => ({
289
+ visible: false,
290
+ top: 0,
291
+ height: 0,
292
+ duration: 0,
293
+ timingFunction: INDICATOR_TRANSITION_TIMING,
294
+ });
295
+
296
+ const pruneLinkRefs = (items: TocItem[]): void => {
297
+ const ids = new Set(items.map((item) => item.id));
298
+ for (const id of linkRefs.keys()) {
299
+ if (!ids.has(id)) linkRefs.delete(id);
300
+ }
301
+ };
302
+
303
+ const setLinkCurrent = (id: string, active: boolean): void => {
304
+ const link = linkRefs.get(id);
305
+ if (!link) return;
306
+ if (active) {
307
+ if (link.getAttribute('aria-current') !== 'location') {
308
+ link.setAttribute('aria-current', 'location');
309
+ }
310
+ return;
311
+ }
312
+ if (link.hasAttribute('aria-current')) link.removeAttribute('aria-current');
313
+ };
314
+
315
+ const syncActiveMarker = (nextId: string | null): void => {
316
+ if (activeLinkId === nextId) return;
317
+ const previousId = activeLinkId;
318
+ activeLinkId = nextId;
319
+ if (previousId) setLinkCurrent(previousId, false);
320
+ if (nextId) setLinkCurrent(nextId, true);
321
+ };
322
+
323
+ const updateIndicatorForItem = (item: TocItem | null): number => {
324
+ if (!toc.runtime.built || !listElement || !item) {
325
+ setIndicator(hiddenIndicator());
326
+ return 0;
327
+ }
328
+ const link = linkRefs.get(item.id);
329
+ if (!link) {
330
+ setIndicator(hiddenIndicator());
331
+ return -1;
332
+ }
333
+ if (listHeight <= 0) {
334
+ const listRect = listElement.getBoundingClientRect();
335
+ listHeight = listElement.offsetHeight || listRect.height;
336
+ }
337
+ const linkRect = link.getBoundingClientRect();
338
+ const linkHeight = link.offsetHeight || linkRect.height;
339
+ const linkTop = link.offsetTop;
340
+ const height = (linkHeight * settings.indicator.heightRatio) / 10;
341
+ const top = linkTop + (linkHeight - height) / 2;
342
+ const distance = state.indicator.visible
343
+ ? Math.abs(top - state.indicator.top)
344
+ : 0;
345
+ const duration =
346
+ distance > 0 && listHeight > 0
347
+ ? Math.min(
348
+ INDICATOR_TRANSITION_MAX_MS,
349
+ Math.max(
350
+ INDICATOR_TRANSITION_MIN_MS,
351
+ Math.round((distance / listHeight) * INDICATOR_TRANSITION_MAX_MS)
352
+ )
353
+ )
354
+ : 0;
355
+ setIndicator({
356
+ visible: height > 0,
357
+ top,
358
+ height,
359
+ duration,
360
+ timingFunction: INDICATOR_TRANSITION_TIMING,
361
+ });
362
+ return duration;
363
+ };
364
+
365
+ const syncListLayout = (): void => {
366
+ const items = getItems();
367
+ if (!toc.runtime.built || !listElement || items.length === 0) {
368
+ listHeight = 0;
369
+ setIndicator(hiddenIndicator());
370
+ return;
371
+ }
372
+ pruneLinkRefs(items);
373
+ const listRect = listElement.getBoundingClientRect();
374
+ listHeight = listElement.offsetHeight || listRect.height;
375
+ const current = items[state.current.index] || null;
376
+ updateIndicatorForItem(current);
377
+ };
378
+
379
+ const scheduleListLayoutSync = (): void => {
380
+ if (runtime.layoutFrameId) return;
381
+ runtime.layoutFrameId = requestAnimationFrame(() => {
382
+ runtime.layoutFrameId = 0;
383
+ untrack(syncListLayout);
384
+ });
385
+ };
386
+
387
+ const bindLinkRef = (item: TocItem, element: HTMLElement): void => {
388
+ linkRefs.set(item.id, element);
389
+ if (item.id === activeLinkId) {
390
+ element.setAttribute('aria-current', 'location');
391
+ } else {
392
+ element.removeAttribute('aria-current');
393
+ }
394
+ scheduleListLayoutSync();
395
+ };
396
+
397
+ const getScrollY = (): number => window.scrollY || window.pageYOffset || 0;
398
+
399
+ const getClockTime = (): number => window.performance?.now?.() ?? Date.now();
400
+
401
+ const clearPendingScroll = (): void => {
402
+ runtime.pendingScroll = null;
403
+ };
404
+
405
+ const beginPendingScroll = (
406
+ index: number,
407
+ top: number,
408
+ duration: number
409
+ ): void => {
410
+ if (!Number.isInteger(index) || index < 0) return;
411
+ runtime.pendingScroll = {
412
+ index,
413
+ top,
414
+ deadline:
415
+ getClockTime() + Math.max(PROGRAMMATIC_SCROLL_TIMEOUT_MS, duration),
416
+ };
417
+ };
165
418
 
166
- const setActive = (index: number): void => {
167
- if (index === state.current.index) return;
168
- const current = state.items[index] || null;
419
+ const shouldSkipScrollActivation = (): boolean => {
420
+ const pending = runtime.pendingScroll;
421
+ if (!pending) return false;
422
+ if (Math.abs(getScrollY() - pending.top) <= SCROLL_REACHED_TOLERANCE) {
423
+ clearPendingScroll();
424
+ return true;
425
+ }
426
+ if (getClockTime() > pending.deadline) {
427
+ clearPendingScroll();
428
+ return false;
429
+ }
430
+ return true;
431
+ };
432
+
433
+ const setActive = (index: number): number => {
434
+ const items = getItems();
435
+ if (index === state.current.index) {
436
+ const current = items[index] || null;
437
+ const duration = updateIndicatorForItem(current);
438
+ if (duration < 0) scheduleListLayoutSync();
439
+ return duration;
440
+ }
441
+ const current = items[index] || null;
169
442
  flushSync(() => {
170
443
  state.current = { index, item: current };
171
444
  });
445
+ syncActiveMarker(current?.id ?? null);
446
+ const duration = updateIndicatorForItem(current);
447
+ if (duration < 0) scheduleListLayoutSync();
172
448
  settings.onChange?.(current, index, toc);
449
+ return duration;
173
450
  };
174
451
 
175
452
  const updateActive = (): void => {
176
453
  if (!toc.runtime.built) return;
454
+ if (shouldSkipScrollActivation()) return;
177
455
  let index = -1;
178
456
  const activeOffset = settings.offset + ACTIVE_OFFSET_TOLERANCE;
179
457
  for (let current = headings.length - 1; current >= 0; current--) {
@@ -211,13 +489,16 @@ export function createToc(props: TocProps = {}): TocInstance {
211
489
  ): void => {
212
490
  const heading = headings.find((element) => element.id === item.id);
213
491
  if (!heading) return;
214
- const scrollY = window.scrollY || window.pageYOffset || 0;
492
+ const scrollY = getScrollY();
215
493
  const top = Math.max(
216
494
  0,
217
495
  heading.getBoundingClientRect().top + scrollY - settings.offset
218
496
  );
497
+ if (activeIndex >= 0) {
498
+ const duration = setActive(activeIndex);
499
+ beginPendingScroll(activeIndex, top, duration);
500
+ }
219
501
  window.scrollTo({ top, behavior: 'smooth' });
220
- if (activeIndex >= 0) setActive(activeIndex);
221
502
  if (updateHash && window.history?.pushState) {
222
503
  window.history.pushState(null, '', `#${item.id}`);
223
504
  }
@@ -226,22 +507,26 @@ export function createToc(props: TocProps = {}): TocInstance {
226
507
  const syncFromTarget = (): void => {
227
508
  if (toc.runtime.destroyed || !toc.runtime.built || !target) return;
228
509
  headings = all<HTMLHeadingElement>(settings.headings, target);
229
- const items = headings.map(normalizeHeading);
510
+ clearPendingScroll();
511
+ const items = normalizeHeadings(headings);
512
+ syncActiveMarker(null);
230
513
  flushSync(() => {
231
514
  state.items = items;
232
515
  state.current = { index: -1, item: null };
516
+ state.indicator = hiddenIndicator();
233
517
  });
234
518
  updateActive();
235
519
  };
236
520
 
237
521
  const activate = (index: number): TocInstance => {
522
+ const items = getItems();
238
523
  if (
239
524
  toc.runtime.built &&
240
525
  Number.isInteger(index) &&
241
526
  index >= 0 &&
242
- index < state.items.length
527
+ index < items.length
243
528
  ) {
244
- scrollToItem(state.items[index]);
529
+ scrollToItem(items[index], { activeIndex: index });
245
530
  }
246
531
  return toc;
247
532
  };
@@ -251,48 +536,98 @@ export function createToc(props: TocProps = {}): TocInstance {
251
536
  props: settings,
252
537
  state,
253
538
  actions: { activate },
254
- view: () =>
255
- jsx('nav', {
539
+ view: () => {
540
+ createEffect(() => {
541
+ const items = getItems();
542
+ void items.map((item) => item.id).join('\0');
543
+ untrack(() => {
544
+ pruneLinkRefs(items);
545
+ if (items.length === 0) {
546
+ listElement = null;
547
+ listHeight = 0;
548
+ linkRefs.clear();
549
+ syncActiveMarker(null);
550
+ setIndicator(hiddenIndicator());
551
+ }
552
+ scheduleListLayoutSync();
553
+ });
554
+ });
555
+ const root = jsx('nav', {
256
556
  className: settings.className.toc,
257
557
  'data-toc': 'root',
258
- children: jsx('div', {
259
- className: settings.className.list,
260
- 'data-toc-list': 'root',
261
- children: For({
262
- each: renderableItems,
263
- key: (item: TocItem) => item.id,
264
- children: (
265
- itemAccessor: () => TocItem,
266
- indexAccessor: () => number
267
- ) =>
268
- jsx('a', {
269
- className: () => {
270
- const item = itemAccessor();
271
- return joinClasses(
272
- settings.className.link,
273
- `${settings.className.levelPrefix}${item.level}`,
274
- state.current.index === indexAccessor()
275
- ? settings.className.active
276
- : ''
277
- );
278
- },
279
- href: () => `#${itemAccessor().id}`,
280
- 'data-toc-index': () => String(indexAccessor()),
281
- 'data-toc-target': () => itemAccessor().id,
282
- 'data-active': () =>
283
- state.current.index === indexAccessor() ? '1' : '0',
284
- onClick: (event: Event) => {
285
- event.preventDefault();
286
- scrollToItem(itemAccessor(), {
287
- activeIndex: indexAccessor(),
288
- updateHash: true,
289
- });
290
- },
291
- children: () => itemAccessor().text,
558
+ }) as HTMLElement;
559
+ insert(
560
+ root,
561
+ Show({
562
+ when: hasItems,
563
+ children: () => {
564
+ const titleElement = settings.title
565
+ ? jsx('div', {
566
+ className: settings.className.title,
567
+ 'data-toc-title': '',
568
+ children: translate('toc'),
569
+ })
570
+ : null;
571
+ const listView = jsx('div', {
572
+ ref: (element: HTMLElement) => {
573
+ listElement = element;
574
+ scheduleListLayoutSync();
575
+ },
576
+ className: settings.className.list,
577
+ 'data-toc-list': 'root',
578
+ children: For({
579
+ each: renderableItems,
580
+ key: (item: TocItem) => item.id,
581
+ children: (itemAccessor: () => TocItem) =>
582
+ jsx('a', {
583
+ ref: (element: HTMLElement) =>
584
+ bindLinkRef(itemAccessor(), element),
585
+ className: settings.className.link,
586
+ href: () => `#${itemAccessor().id}`,
587
+ 'data-toc-level': () => String(itemAccessor().level),
588
+ onClick: (event: Event) => {
589
+ event.preventDefault();
590
+ const item = itemAccessor();
591
+ scrollToItem(item, {
592
+ activeIndex: getItems().findIndex(
593
+ (current) => current.id === item.id
594
+ ),
595
+ updateHash: true,
596
+ });
597
+ },
598
+ children: () => itemAccessor().text,
599
+ }),
292
600
  }),
293
- }),
294
- }),
295
- }) as HTMLElement,
601
+ }) as HTMLElement;
602
+ insert(
603
+ listView,
604
+ Show({
605
+ when: hasItems,
606
+ children: () =>
607
+ jsx('div', {
608
+ className: settings.className.indicator,
609
+ 'data-toc-indicator': '',
610
+ hidden: () => !state.indicator.visible,
611
+ style: () => ({
612
+ pointerEvents: 'none',
613
+ position: 'absolute',
614
+ left: settings.indicator.left,
615
+ top: `${state.indicator.top}px`,
616
+ width: settings.indicator.width,
617
+ height: `${state.indicator.height}px`,
618
+ transition: `top ${state.indicator.duration}ms ${state.indicator.timingFunction}`,
619
+ borderRadius: settings.indicator.radius,
620
+ backgroundColor: settings.indicator.color,
621
+ }),
622
+ }),
623
+ })
624
+ );
625
+ return settings.title ? [titleElement, listView] : listView;
626
+ },
627
+ })
628
+ );
629
+ return root;
630
+ },
296
631
  onBuild(context) {
297
632
  target = requireContainer(settings.target, 'Toc.target');
298
633
  events.on('scroll', window, 'scroll', onScroll, { passive: true });
@@ -314,14 +649,20 @@ export function createToc(props: TocProps = {}): TocInstance {
314
649
  onDestroy() {
315
650
  if (runtime.frameId) cancelAnimationFrame(runtime.frameId);
316
651
  if (runtime.refreshFrameId) cancelAnimationFrame(runtime.refreshFrameId);
652
+ if (runtime.layoutFrameId) cancelAnimationFrame(runtime.layoutFrameId);
317
653
  runtime.frameId = 0;
318
654
  runtime.refreshFrameId = 0;
655
+ runtime.layoutFrameId = 0;
656
+ clearPendingScroll();
319
657
  runtime.ticking = false;
320
658
  runtime.refreshing = false;
321
659
  observer?.disconnect();
322
660
  observer = null;
323
661
  target = null;
324
662
  headings = [];
663
+ listElement = null;
664
+ listHeight = 0;
665
+ linkRefs.clear();
325
666
  },
326
667
  });
327
668
 
@@ -22,7 +22,6 @@
22
22
  @import url('./pagination.css');
23
23
  @import url('./card.css');
24
24
  @import url('./menu.css');
25
- @import url('./toc.css');
26
25
  @import url('./tree.css');
27
26
  @import url('./form.css');
28
27
  @import url('./toolbar.css');
@@ -139,7 +139,7 @@
139
139
  }
140
140
 
141
141
  .input-inner-group .el-addon + .j-input {
142
- padding-inline: var(--space);
142
+ padding-inline: 0 var(--space);
143
143
  }
144
144
 
145
145
  .input-inner-group .el-addon + .j-select {