vanilla-jui 1.7.6 → 1.7.7

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.7",
4
4
  "private": false,
5
5
  "description": "A lightweight, reactive, and fully customizable vanilla JS UI library.",
6
6
  "keywords": [
@@ -1,31 +1,28 @@
1
1
  import {
2
2
  For,
3
3
  createDeepStore,
4
+ createEffect,
4
5
  createMemo,
5
6
  flushSync,
6
7
  jsx,
8
+ untrack,
7
9
  } from 'vanilla-signal';
8
10
 
9
11
  import {
10
12
  type FunctionalComponent,
11
13
  defineComponent,
12
14
  } from '../core/component.ts';
13
- import {
14
- type DOMReference,
15
- all,
16
- joinClasses,
17
- requireContainer,
18
- } from '../utilities/dom.ts';
15
+ import { type DOMReference, all, requireContainer } from '../utilities/dom.ts';
19
16
  import { createEventManager } from '../utilities/events.ts';
20
- import { randomId } from '../utilities/id.ts';
21
17
  import { type ConfigSchema, resolveConfig } from '../utilities/config.ts';
18
+ import { translate } from '../utilities/locale.ts';
22
19
 
23
20
  interface TocClassNames {
24
21
  toc: string;
22
+ title: string;
25
23
  list: string;
26
24
  link: string;
27
- active: string;
28
- levelPrefix: string;
25
+ indicator: string;
29
26
  }
30
27
 
31
28
  type TocClassNameConfig = Partial<TocClassNames>;
@@ -41,11 +38,22 @@ export interface TocCurrent {
41
38
  item: TocItem | null;
42
39
  }
43
40
 
41
+ export interface TocIndicator {
42
+ visible: boolean;
43
+ top: number;
44
+ height: number;
45
+ duration: number;
46
+ timingFunction: string;
47
+ }
48
+
44
49
  interface TocProps extends Record<string, unknown> {
45
50
  target?: DOMReference;
46
51
  headings?: string;
47
52
  offset?: number;
48
53
  reactive?: boolean;
54
+ title?: boolean;
55
+ indicatorWidth?: string;
56
+ indicatorHeightRatio?: number;
49
57
  className?: TocClassNameConfig;
50
58
  onChange?:
51
59
  | ((item: TocItem | null, index: number, toc: TocInstance) => void)
@@ -57,6 +65,9 @@ interface ResolvedTocProps extends Record<string, unknown> {
57
65
  headings: string;
58
66
  offset: number;
59
67
  reactive: boolean;
68
+ title: boolean;
69
+ indicatorWidth: string;
70
+ indicatorHeightRatio: number;
60
71
  className: TocClassNames;
61
72
  onChange:
62
73
  | ((item: TocItem | null, index: number, toc: TocInstance) => void)
@@ -66,6 +77,7 @@ interface ResolvedTocProps extends Record<string, unknown> {
66
77
  interface TocState extends Record<string, unknown> {
67
78
  items: TocItem[];
68
79
  current: TocCurrent;
80
+ indicator: TocIndicator;
69
81
  }
70
82
 
71
83
  interface TocRuntimeExtras {
@@ -73,6 +85,8 @@ interface TocRuntimeExtras {
73
85
  frameId: number;
74
86
  refreshing: boolean;
75
87
  refreshFrameId: number;
88
+ layoutFrameId: number;
89
+ pendingScroll: TocPendingScroll | null;
76
90
  }
77
91
 
78
92
  interface TocScrollOptions {
@@ -80,6 +94,12 @@ interface TocScrollOptions {
80
94
  updateHash?: boolean;
81
95
  }
82
96
 
97
+ interface TocPendingScroll {
98
+ index: number;
99
+ top: number;
100
+ deadline: number;
101
+ }
102
+
83
103
  interface TocActions {
84
104
  activate(index: number): TocInstance;
85
105
  }
@@ -93,10 +113,10 @@ type TocInstance = FunctionalComponent<
93
113
 
94
114
  const DEFAULT_CLASS_NAMES: TocClassNames = {
95
115
  toc: 'j-toc',
116
+ title: 'toc-title',
96
117
  list: 'toc-list',
97
118
  link: 'toc-link',
98
- active: 'is-active',
99
- levelPrefix: 'is-level-',
119
+ indicator: 'toc-indicator',
100
120
  };
101
121
 
102
122
  const TOC_PROPS_SCHEMA = {
@@ -108,6 +128,14 @@ const TOC_PROPS_SCHEMA = {
108
128
  min: 0,
109
129
  },
110
130
  reactive: { default: false, type: 'boolean' },
131
+ title: { default: false, type: 'boolean' },
132
+ indicatorWidth: { default: '2px', type: 'string' },
133
+ indicatorHeightRatio: {
134
+ default: 5,
135
+ type: 'number',
136
+ min: 1,
137
+ max: 10,
138
+ },
111
139
  className: {
112
140
  default: DEFAULT_CLASS_NAMES,
113
141
  type: 'plainObject',
@@ -117,6 +145,11 @@ const TOC_PROPS_SCHEMA = {
117
145
  } satisfies ConfigSchema<TocProps>;
118
146
 
119
147
  const ACTIVE_OFFSET_TOLERANCE = 1;
148
+ const INDICATOR_TRANSITION_MAX_MS = 1200;
149
+ const INDICATOR_TRANSITION_MIN_MS = 120;
150
+ const INDICATOR_TRANSITION_TIMING = 'ease-in-out';
151
+ const PROGRAMMATIC_SCROLL_TIMEOUT_MS = 2400;
152
+ const SCROLL_REACHED_TOLERANCE = 2;
120
153
 
121
154
  function normalizeProps(input: TocProps): ResolvedTocProps {
122
155
  const props = resolveConfig(input, TOC_PROPS_SCHEMA, 'Toc.props');
@@ -125,6 +158,9 @@ function normalizeProps(input: TocProps): ResolvedTocProps {
125
158
  headings: props.headings as string,
126
159
  offset: props.offset as number,
127
160
  reactive: props.reactive as boolean,
161
+ title: props.title as boolean,
162
+ indicatorWidth: props.indicatorWidth as string,
163
+ indicatorHeightRatio: props.indicatorHeightRatio as number,
128
164
  className: props.className as TocClassNames,
129
165
  onChange: props.onChange as ResolvedTocProps['onChange'],
130
166
  };
@@ -135,45 +171,255 @@ function resolveHeadingLevel(element: Element): number {
135
171
  return match ? Number(match[1]) : 1;
136
172
  }
137
173
 
138
- function normalizeHeading(element: HTMLHeadingElement, index: number): TocItem {
139
- if (!element.id) element.id = `toc-${randomId()}-${index}`;
174
+ function normalizeHeadingId(
175
+ element: HTMLHeadingElement,
176
+ index: number,
177
+ usedIds: Set<string>
178
+ ): string {
179
+ const textId = (element.textContent || '').trim().replace(/\s+/g, '-');
180
+ const base = element.id || textId || `heading-${index + 1}`;
181
+ let id = base;
182
+ let suffix = 1;
183
+ while (usedIds.has(id)) {
184
+ id = `${base}-${suffix}`;
185
+ suffix += 1;
186
+ }
187
+ element.id = id;
188
+ usedIds.add(id);
189
+ return id;
190
+ }
191
+
192
+ function normalizeHeading(
193
+ element: HTMLHeadingElement,
194
+ index: number,
195
+ usedIds: Set<string>
196
+ ): TocItem {
140
197
  return {
141
- id: element.id,
198
+ id: normalizeHeadingId(element, index, usedIds),
142
199
  text: element.textContent || '',
143
200
  level: resolveHeadingLevel(element),
144
201
  };
145
202
  }
146
203
 
204
+ function normalizeHeadings(elements: HTMLHeadingElement[]): TocItem[] {
205
+ const usedIds = new Set<string>();
206
+ return elements.map((element, index) =>
207
+ normalizeHeading(element, index, usedIds)
208
+ );
209
+ }
210
+
147
211
  export function createToc(props: TocProps = {}): TocInstance {
148
212
  const settings = normalizeProps(props);
149
213
  const state = createDeepStore({
150
214
  items: [],
151
215
  current: { index: -1, item: null },
216
+ indicator: {
217
+ visible: false,
218
+ top: 0,
219
+ height: 0,
220
+ duration: 0,
221
+ timingFunction: INDICATOR_TRANSITION_TIMING,
222
+ },
152
223
  }) as TocState;
153
224
  const runtime: TocRuntimeExtras = {
154
225
  ticking: false,
155
226
  frameId: 0,
156
227
  refreshing: false,
157
228
  refreshFrameId: 0,
229
+ layoutFrameId: 0,
230
+ pendingScroll: null,
158
231
  };
159
232
  const events = createEventManager();
160
233
  let target: Element | null = null;
161
234
  let headings: HTMLHeadingElement[] = [];
162
235
  let observer: MutationObserver | null = null;
236
+ let listElement: HTMLElement | null = null;
163
237
  let toc: TocInstance;
238
+ let activeLinkId: string | null = null;
239
+ let listHeight = 0;
240
+ const linkRefs = new Map<string, HTMLElement>();
164
241
  const renderableItems = createMemo(() => state.items);
165
242
 
166
- const setActive = (index: number): void => {
167
- if (index === state.current.index) return;
243
+ const setIndicator = (indicator: TocIndicator): void => {
244
+ if (
245
+ state.indicator.visible === indicator.visible &&
246
+ state.indicator.top === indicator.top &&
247
+ state.indicator.height === indicator.height &&
248
+ state.indicator.duration === indicator.duration &&
249
+ state.indicator.timingFunction === indicator.timingFunction
250
+ ) {
251
+ return;
252
+ }
253
+ flushSync(() => {
254
+ state.indicator = indicator;
255
+ });
256
+ };
257
+
258
+ const hiddenIndicator = (): TocIndicator => ({
259
+ visible: false,
260
+ top: 0,
261
+ height: 0,
262
+ duration: 0,
263
+ timingFunction: INDICATOR_TRANSITION_TIMING,
264
+ });
265
+
266
+ const pruneLinkRefs = (items: TocItem[]): void => {
267
+ const ids = new Set(items.map((item) => item.id));
268
+ for (const id of linkRefs.keys()) {
269
+ if (!ids.has(id)) linkRefs.delete(id);
270
+ }
271
+ };
272
+
273
+ const setLinkCurrent = (id: string, active: boolean): void => {
274
+ const link = linkRefs.get(id);
275
+ if (!link) return;
276
+ if (active) {
277
+ if (link.getAttribute('aria-current') !== 'location') {
278
+ link.setAttribute('aria-current', 'location');
279
+ }
280
+ return;
281
+ }
282
+ if (link.hasAttribute('aria-current')) link.removeAttribute('aria-current');
283
+ };
284
+
285
+ const syncActiveMarker = (nextId: string | null): void => {
286
+ if (activeLinkId === nextId) return;
287
+ const previousId = activeLinkId;
288
+ activeLinkId = nextId;
289
+ if (previousId) setLinkCurrent(previousId, false);
290
+ if (nextId) setLinkCurrent(nextId, true);
291
+ };
292
+
293
+ const updateIndicatorForItem = (item: TocItem | null): number => {
294
+ if (!toc.runtime.built || !listElement || !item) {
295
+ setIndicator(hiddenIndicator());
296
+ return 0;
297
+ }
298
+ const link = linkRefs.get(item.id);
299
+ if (!link) {
300
+ setIndicator(hiddenIndicator());
301
+ return -1;
302
+ }
303
+ if (listHeight <= 0) {
304
+ const listRect = listElement.getBoundingClientRect();
305
+ listHeight = listElement.offsetHeight || listRect.height;
306
+ }
307
+ const linkRect = link.getBoundingClientRect();
308
+ const linkHeight = link.offsetHeight || linkRect.height;
309
+ const linkTop = link.offsetTop;
310
+ const height = (linkHeight * settings.indicatorHeightRatio) / 10;
311
+ const top = linkTop + (linkHeight - height) / 2;
312
+ const distance = state.indicator.visible
313
+ ? Math.abs(top - state.indicator.top)
314
+ : 0;
315
+ const duration =
316
+ distance > 0 && listHeight > 0
317
+ ? Math.min(
318
+ INDICATOR_TRANSITION_MAX_MS,
319
+ Math.max(
320
+ INDICATOR_TRANSITION_MIN_MS,
321
+ Math.round((distance / listHeight) * INDICATOR_TRANSITION_MAX_MS)
322
+ )
323
+ )
324
+ : 0;
325
+ setIndicator({
326
+ visible: height > 0,
327
+ top,
328
+ height,
329
+ duration,
330
+ timingFunction: INDICATOR_TRANSITION_TIMING,
331
+ });
332
+ return duration;
333
+ };
334
+
335
+ const syncListLayout = (): void => {
336
+ if (!toc.runtime.built || !listElement || state.items.length === 0) {
337
+ listHeight = 0;
338
+ setIndicator(hiddenIndicator());
339
+ return;
340
+ }
341
+ pruneLinkRefs(state.items);
342
+ const listRect = listElement.getBoundingClientRect();
343
+ listHeight = listElement.offsetHeight || listRect.height;
344
+ const current = state.items[state.current.index] || null;
345
+ updateIndicatorForItem(current);
346
+ };
347
+
348
+ const scheduleListLayoutSync = (): void => {
349
+ if (runtime.layoutFrameId) return;
350
+ runtime.layoutFrameId = requestAnimationFrame(() => {
351
+ runtime.layoutFrameId = 0;
352
+ untrack(syncListLayout);
353
+ });
354
+ };
355
+
356
+ const bindLinkRef = (item: TocItem, element: HTMLElement): void => {
357
+ linkRefs.set(item.id, element);
358
+ if (item.id === activeLinkId) {
359
+ element.setAttribute('aria-current', 'location');
360
+ } else {
361
+ element.removeAttribute('aria-current');
362
+ }
363
+ scheduleListLayoutSync();
364
+ };
365
+
366
+ const getScrollY = (): number => window.scrollY || window.pageYOffset || 0;
367
+
368
+ const getClockTime = (): number => window.performance?.now?.() ?? Date.now();
369
+
370
+ const clearPendingScroll = (): void => {
371
+ runtime.pendingScroll = null;
372
+ };
373
+
374
+ const beginPendingScroll = (
375
+ index: number,
376
+ top: number,
377
+ duration: number
378
+ ): void => {
379
+ if (!Number.isInteger(index) || index < 0) return;
380
+ runtime.pendingScroll = {
381
+ index,
382
+ top,
383
+ deadline:
384
+ getClockTime() + Math.max(PROGRAMMATIC_SCROLL_TIMEOUT_MS, duration),
385
+ };
386
+ };
387
+
388
+ const shouldSkipScrollActivation = (): boolean => {
389
+ const pending = runtime.pendingScroll;
390
+ if (!pending) return false;
391
+ if (Math.abs(getScrollY() - pending.top) <= SCROLL_REACHED_TOLERANCE) {
392
+ clearPendingScroll();
393
+ return true;
394
+ }
395
+ if (getClockTime() > pending.deadline) {
396
+ clearPendingScroll();
397
+ return false;
398
+ }
399
+ return true;
400
+ };
401
+
402
+ const setActive = (index: number): number => {
403
+ if (index === state.current.index) {
404
+ const current = state.items[index] || null;
405
+ const duration = updateIndicatorForItem(current);
406
+ if (duration < 0) scheduleListLayoutSync();
407
+ return duration;
408
+ }
168
409
  const current = state.items[index] || null;
169
410
  flushSync(() => {
170
411
  state.current = { index, item: current };
171
412
  });
413
+ syncActiveMarker(current?.id ?? null);
414
+ const duration = updateIndicatorForItem(current);
415
+ if (duration < 0) scheduleListLayoutSync();
172
416
  settings.onChange?.(current, index, toc);
417
+ return duration;
173
418
  };
174
419
 
175
420
  const updateActive = (): void => {
176
421
  if (!toc.runtime.built) return;
422
+ if (shouldSkipScrollActivation()) return;
177
423
  let index = -1;
178
424
  const activeOffset = settings.offset + ACTIVE_OFFSET_TOLERANCE;
179
425
  for (let current = headings.length - 1; current >= 0; current--) {
@@ -211,13 +457,16 @@ export function createToc(props: TocProps = {}): TocInstance {
211
457
  ): void => {
212
458
  const heading = headings.find((element) => element.id === item.id);
213
459
  if (!heading) return;
214
- const scrollY = window.scrollY || window.pageYOffset || 0;
460
+ const scrollY = getScrollY();
215
461
  const top = Math.max(
216
462
  0,
217
463
  heading.getBoundingClientRect().top + scrollY - settings.offset
218
464
  );
465
+ if (activeIndex >= 0) {
466
+ const duration = setActive(activeIndex);
467
+ beginPendingScroll(activeIndex, top, duration);
468
+ }
219
469
  window.scrollTo({ top, behavior: 'smooth' });
220
- if (activeIndex >= 0) setActive(activeIndex);
221
470
  if (updateHash && window.history?.pushState) {
222
471
  window.history.pushState(null, '', `#${item.id}`);
223
472
  }
@@ -226,10 +475,13 @@ export function createToc(props: TocProps = {}): TocInstance {
226
475
  const syncFromTarget = (): void => {
227
476
  if (toc.runtime.destroyed || !toc.runtime.built || !target) return;
228
477
  headings = all<HTMLHeadingElement>(settings.headings, target);
229
- const items = headings.map(normalizeHeading);
478
+ clearPendingScroll();
479
+ const items = normalizeHeadings(headings);
480
+ syncActiveMarker(null);
230
481
  flushSync(() => {
231
482
  state.items = items;
232
483
  state.current = { index: -1, item: null };
484
+ state.indicator = hiddenIndicator();
233
485
  });
234
486
  updateActive();
235
487
  };
@@ -241,7 +493,7 @@ export function createToc(props: TocProps = {}): TocInstance {
241
493
  index >= 0 &&
242
494
  index < state.items.length
243
495
  ) {
244
- scrollToItem(state.items[index]);
496
+ scrollToItem(state.items[index], { activeIndex: index });
245
497
  }
246
498
  return toc;
247
499
  };
@@ -251,14 +503,31 @@ export function createToc(props: TocProps = {}): TocInstance {
251
503
  props: settings,
252
504
  state,
253
505
  actions: { activate },
254
- view: () =>
255
- jsx('nav', {
256
- className: settings.className.toc,
257
- 'data-toc': 'root',
258
- children: jsx('div', {
259
- className: settings.className.list,
260
- 'data-toc-list': 'root',
261
- children: For({
506
+ view: () => {
507
+ createEffect(() => {
508
+ const items = state.items;
509
+ void items.map((item) => item.id).join('\0');
510
+ untrack(() => {
511
+ pruneLinkRefs(items);
512
+ scheduleListLayoutSync();
513
+ });
514
+ });
515
+ const titleElement = settings.title
516
+ ? jsx('div', {
517
+ className: settings.className.title,
518
+ 'data-toc-title': '',
519
+ children: translate('toc'),
520
+ })
521
+ : null;
522
+ const listView = jsx('div', {
523
+ ref: (element: HTMLElement) => {
524
+ listElement = element;
525
+ scheduleListLayoutSync();
526
+ },
527
+ className: settings.className.list,
528
+ 'data-toc-list': 'root',
529
+ children: [
530
+ For({
262
531
  each: renderableItems,
263
532
  key: (item: TocItem) => item.id,
264
533
  children: (
@@ -266,33 +535,51 @@ export function createToc(props: TocProps = {}): TocInstance {
266
535
  indexAccessor: () => number
267
536
  ) =>
268
537
  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
- },
538
+ ref: (element: HTMLElement) =>
539
+ bindLinkRef(itemAccessor(), element),
540
+ className: settings.className.link,
279
541
  href: () => `#${itemAccessor().id}`,
280
- 'data-toc-index': () => String(indexAccessor()),
542
+ 'data-toc-link': () => String(indexAccessor()),
281
543
  'data-toc-target': () => itemAccessor().id,
282
- 'data-active': () =>
283
- state.current.index === indexAccessor() ? '1' : '0',
544
+ 'data-toc-level': () => String(itemAccessor().level),
284
545
  onClick: (event: Event) => {
285
546
  event.preventDefault();
286
- scrollToItem(itemAccessor(), {
287
- activeIndex: indexAccessor(),
547
+ const item = itemAccessor();
548
+ scrollToItem(item, {
549
+ activeIndex: state.items.findIndex(
550
+ (current) => current.id === item.id
551
+ ),
288
552
  updateHash: true,
289
553
  });
290
554
  },
291
555
  children: () => itemAccessor().text,
292
556
  }),
293
557
  }),
294
- }),
295
- }) as HTMLElement,
558
+ For({
559
+ each: () => (state.items.length > 0 ? [true] : []),
560
+ key: () => 'toc-indicator',
561
+ children: () =>
562
+ jsx('div', {
563
+ className: settings.className.indicator,
564
+ 'data-toc-indicator': '',
565
+ hidden: () => !state.indicator.visible,
566
+ style: () => ({
567
+ top: `${state.indicator.top}px`,
568
+ width: settings.indicatorWidth,
569
+ height: `${state.indicator.height}px`,
570
+ transitionDuration: `${state.indicator.duration}ms`,
571
+ transitionTimingFunction: state.indicator.timingFunction,
572
+ }),
573
+ }),
574
+ }),
575
+ ],
576
+ });
577
+ return jsx('nav', {
578
+ className: settings.className.toc,
579
+ 'data-toc': 'root',
580
+ children: settings.title ? [titleElement, listView] : listView,
581
+ }) as HTMLElement;
582
+ },
296
583
  onBuild(context) {
297
584
  target = requireContainer(settings.target, 'Toc.target');
298
585
  events.on('scroll', window, 'scroll', onScroll, { passive: true });
@@ -314,14 +601,20 @@ export function createToc(props: TocProps = {}): TocInstance {
314
601
  onDestroy() {
315
602
  if (runtime.frameId) cancelAnimationFrame(runtime.frameId);
316
603
  if (runtime.refreshFrameId) cancelAnimationFrame(runtime.refreshFrameId);
604
+ if (runtime.layoutFrameId) cancelAnimationFrame(runtime.layoutFrameId);
317
605
  runtime.frameId = 0;
318
606
  runtime.refreshFrameId = 0;
607
+ runtime.layoutFrameId = 0;
608
+ clearPendingScroll();
319
609
  runtime.ticking = false;
320
610
  runtime.refreshing = false;
321
611
  observer?.disconnect();
322
612
  observer = null;
323
613
  target = null;
324
614
  headings = [];
615
+ listElement = null;
616
+ listHeight = 0;
617
+ linkRefs.clear();
325
618
  },
326
619
  });
327
620
 
@@ -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 {
@@ -23,6 +23,7 @@
23
23
  border-radius: var(--radius-md);
24
24
  min-width: 200px;
25
25
  max-width: 310px;
26
+ backdrop-filter: blur(4px);
26
27
  }
27
28
 
28
29
  .j-toast .el-icon {
@@ -31,25 +32,25 @@
31
32
 
32
33
  .j-toast.is-success {
33
34
  --toast-fg: var(--state-success);
34
- --toast-bg: var(--state-success-weak);
35
+ --toast-bg: var(--state-success-soft);
35
36
  --toast-border-color: var(--state-success-border);
36
37
  }
37
38
 
38
39
  .j-toast.is-error {
39
40
  --toast-fg: var(--state-danger);
40
- --toast-bg: var(--state-danger-weak);
41
+ --toast-bg: var(--state-danger-soft);
41
42
  --toast-border-color: var(--state-danger-border);
42
43
  }
43
44
 
44
45
  .j-toast.is-warning {
45
46
  --toast-fg: var(--state-warning);
46
- --toast-bg: var(--state-warning-weak);
47
+ --toast-bg: var(--state-warning-soft);
47
48
  --toast-border-color: var(--state-warning-border);
48
49
  }
49
50
 
50
51
  .j-toast.is-primary {
51
52
  --toast-fg: var(--tone-solid);
52
- --toast-bg: var(--tone-weak);
53
+ --toast-bg: var(--tone-soft);
53
54
  --toast-border-color: var(--tone-border);
54
55
  }
55
56
 
@@ -1,25 +1,40 @@
1
1
  @layer themes {
2
+ .j-toc {
3
+ width: 100%;
4
+ padding-left: 0.75rem;
5
+ }
6
+
7
+ .j-toc .toc-title {
8
+ color: var(--tone-caption);
9
+ margin-bottom: 4px;
10
+ }
11
+
2
12
  .j-toc .toc-link {
3
- padding: 0.375rem 0 0.375rem 0.75rem;
4
- border-left: 2px solid transparent;
13
+ padding-block: 0.375rem;
5
14
  color: var(--tone-caption);
6
15
  font-size: var(--font-size-sm);
7
16
  text-decoration: none;
17
+ display: block;
8
18
  }
9
19
 
10
20
  .j-toc .toc-link:hover,
11
- .j-toc .toc-link.is-active {
21
+ .j-toc .toc-link[aria-current] {
12
22
  color: var(--tone-heading);
13
23
  }
14
24
 
15
- .j-toc .toc-link.is-active {
16
- border-left-color: var(--tone-solid);
25
+ .j-toc .toc-link[data-toc-level='3'],
26
+ .j-toc .toc-link[data-toc-level='4'],
27
+ .j-toc .toc-link[data-toc-level='5'],
28
+ .j-toc .toc-link[data-toc-level='6'] {
29
+ padding-left: 0.75rem;
17
30
  }
18
31
 
19
- .j-toc .toc-link.is-level-3,
20
- .j-toc .toc-link.is-level-4,
21
- .j-toc .toc-link.is-level-5,
22
- .j-toc .toc-link.is-level-6 {
23
- padding-left: 1.5rem;
32
+ .j-toc .toc-indicator {
33
+ position: absolute;
34
+ left: 0;
35
+ background-color: var(--tone-solid);
36
+ transition-property: top;
37
+ pointer-events: none;
38
+ border-radius: 2px;
24
39
  }
25
40
  }