@alphacifer/slidev-addon-theme 0.0.1 → 0.0.3

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.
@@ -0,0 +1,74 @@
1
+ import type { Directive, VNode } from 'vue';
2
+
3
+ interface IShiftingIntroContent {
4
+ readonly key: number;
5
+ readonly node: VNode;
6
+ }
7
+
8
+ export interface IShiftingIntroNodes {
9
+ readonly title: VNode | null;
10
+ readonly clickRef: ReadonlyMap<number, number>;
11
+ readonly contents: readonly IShiftingIntroContent[];
12
+ }
13
+
14
+ const CLICK_DIRECTIVE_NAMES = new Set(['click', 'v-click', 'vClick']);
15
+
16
+ const getDirectiveName = (directive: Directive): string | undefined => {
17
+ if (typeof directive === 'function') {
18
+ return directive.name;
19
+ }
20
+
21
+ if ('name' in directive && typeof directive.name === 'string') {
22
+ return directive.name;
23
+ }
24
+
25
+ return undefined;
26
+ };
27
+
28
+ const hasImplicitClickDirective = (node: VNode): boolean => {
29
+ return Boolean(
30
+ node.dirs?.some(({ dir, value }) => {
31
+ const name = getDirectiveName(dir);
32
+ return (
33
+ name !== undefined &&
34
+ CLICK_DIRECTIVE_NAMES.has(name) &&
35
+ value === undefined
36
+ );
37
+ }),
38
+ );
39
+ };
40
+
41
+ /**
42
+ * Separates the first heading from a shifting-intro slot and remaps implicit
43
+ * v-click directives after the layout's internal reveal click.
44
+ */
45
+ export const collectShiftingIntroNodes = (
46
+ children: readonly VNode[],
47
+ ): IShiftingIntroNodes => {
48
+ let title: VNode | null = null;
49
+ let clickIndex = 1;
50
+ const clickRef = new Map<number, number>();
51
+ const contents: IShiftingIntroContent[] = [];
52
+
53
+ children.forEach((node, key) => {
54
+ if (title === null && node.type === 'h1') {
55
+ title = node;
56
+ } else {
57
+ contents.push({
58
+ key,
59
+ node,
60
+ });
61
+ }
62
+
63
+ if (hasImplicitClickDirective(node)) {
64
+ clickIndex += 1;
65
+ clickRef.set(key, clickIndex);
66
+ }
67
+ });
68
+
69
+ return {
70
+ title,
71
+ clickRef,
72
+ contents,
73
+ };
74
+ };
@@ -0,0 +1,11 @@
1
+ export function calculateTocCenterAlignmentOffsetPx(
2
+ markerCenterY: number,
3
+ articleCenterY: number,
4
+ scaleY: number,
5
+ ): number {
6
+ if (scaleY <= 0) {
7
+ return 0;
8
+ }
9
+
10
+ return (markerCenterY - articleCenterY) / scaleY;
11
+ }
@@ -0,0 +1,118 @@
1
+ export interface ITocMarkerPosition {
2
+ readonly offsetFromMiddlePx: number;
3
+ readonly rightShiftPx: number;
4
+ readonly topPercent: number;
5
+ }
6
+
7
+ export const MAXIMUM_TOC_ITEM_COUNT = 7;
8
+ const TOC_CANVAS_HEIGHT_PX = 552;
9
+ const TOC_VERTICAL_PADDING_PX = 40;
10
+ const ROOT_FONT_SIZE_PX = 16;
11
+ const TOC_ITEM_HEIGHT_REM = 2.3;
12
+ const TOC_ITEM_HEIGHT_PX = TOC_ITEM_HEIGHT_REM * ROOT_FONT_SIZE_PX;
13
+ const MARKER_TOP_PERCENT =
14
+ ((TOC_VERTICAL_PADDING_PX + TOC_ITEM_HEIGHT_PX / 2) / TOC_CANVAS_HEIGHT_PX) *
15
+ 100;
16
+ const MARKER_BOTTOM_PERCENT = 100 - MARKER_TOP_PERCENT;
17
+ const MARKER_HORIZONTAL_CURVE_PX = 9;
18
+ const MARKER_MIDDLE_RIGHT_SHIFT_PX = 24;
19
+ const MARKER_VERTICAL_STEP_PERCENT =
20
+ (MARKER_BOTTOM_PERCENT - MARKER_TOP_PERCENT) / (MAXIMUM_TOC_ITEM_COUNT - 1);
21
+
22
+ export function calculateTocMarkerPositions(
23
+ count: number,
24
+ ): readonly ITocMarkerPosition[] {
25
+ if (!Number.isInteger(count) || count <= 0) {
26
+ return [];
27
+ }
28
+
29
+ const boundedCount = Math.min(count, MAXIMUM_TOC_ITEM_COUNT);
30
+ const middleIndex = (boundedCount - 1) / 2;
31
+ const centerPercent = (MARKER_TOP_PERCENT + MARKER_BOTTOM_PERCENT) / 2;
32
+
33
+ return Array.from({ length: boundedCount }, (_, index) => {
34
+ const distanceFromMiddle = Math.abs(index - middleIndex);
35
+
36
+ return {
37
+ offsetFromMiddlePx:
38
+ distanceFromMiddle * distanceFromMiddle * MARKER_HORIZONTAL_CURVE_PX,
39
+ rightShiftPx: MARKER_MIDDLE_RIGHT_SHIFT_PX,
40
+ topPercent: Number(
41
+ (
42
+ centerPercent +
43
+ (index - middleIndex) * MARKER_VERTICAL_STEP_PERCENT
44
+ ).toFixed(2),
45
+ ),
46
+ };
47
+ });
48
+ }
49
+
50
+ export interface ICalculateTocItemCountParams {
51
+ readonly maximumCount: number;
52
+ readonly availableCount: number;
53
+ }
54
+
55
+ export function calculateTocItemCount({
56
+ maximumCount,
57
+ availableCount,
58
+ }: ICalculateTocItemCountParams): number {
59
+ const normalizedAvailableCount = Math.max(0, Math.trunc(availableCount));
60
+ const normalizedMaximumCount = Number.isFinite(maximumCount)
61
+ ? Math.max(1, Math.min(MAXIMUM_TOC_ITEM_COUNT, Math.trunc(maximumCount)))
62
+ : MAXIMUM_TOC_ITEM_COUNT;
63
+
64
+ return Math.min(normalizedAvailableCount, normalizedMaximumCount);
65
+ }
66
+
67
+ export function calculateTocMarkerCenterPercent(count: number): number {
68
+ const positions = calculateTocMarkerPositions(count);
69
+ const firstPosition = positions[0];
70
+ const lastPosition = positions.at(-1);
71
+
72
+ if (!firstPosition || !lastPosition) {
73
+ return 50;
74
+ }
75
+
76
+ return (firstPosition.topPercent + lastPosition.topPercent) / 2;
77
+ }
78
+
79
+ export function calculateTocMiddleMarkerOffsetPx(count: number): number {
80
+ const positions = calculateTocMarkerPositions(count);
81
+ const middlePosition = positions[Math.floor((positions.length - 1) / 2)];
82
+
83
+ if (!middlePosition) {
84
+ return 0;
85
+ }
86
+
87
+ return middlePosition.rightShiftPx - middlePosition.offsetFromMiddlePx;
88
+ }
89
+
90
+ export function calculateTocArticleOffsetPx(
91
+ position: ITocMarkerPosition,
92
+ ): number {
93
+ const connectorWidthPx = Math.max(
94
+ 22,
95
+ 48 - position.offsetFromMiddlePx * 0.45,
96
+ );
97
+
98
+ return (
99
+ position.rightShiftPx -
100
+ position.offsetFromMiddlePx +
101
+ connectorWidthPx -
102
+ MARKER_MIDDLE_RIGHT_SHIFT_PX
103
+ );
104
+ }
105
+
106
+ export function calculateTocListGapRem(count: number): number {
107
+ if (!Number.isInteger(count) || count <= 1) {
108
+ return 0;
109
+ }
110
+
111
+ const availableCenterSpanPx =
112
+ TOC_CANVAS_HEIGHT_PX - TOC_VERTICAL_PADDING_PX * 2 - TOC_ITEM_HEIGHT_PX;
113
+ const rowHeightRem =
114
+ availableCenterSpanPx / (MAXIMUM_TOC_ITEM_COUNT - 1) / ROOT_FONT_SIZE_PX;
115
+ const gapRem = rowHeightRem - TOC_ITEM_HEIGHT_REM;
116
+
117
+ return Number(gapRem.toFixed(2));
118
+ }
@@ -0,0 +1,10 @@
1
+ const MINIMUM_TOC_ARTICLE_WIDTH_PX = 260;
2
+
3
+ export function calculateUniformTocArticleWidthPx(
4
+ articleWidths: readonly number[],
5
+ ): number {
6
+ return articleWidths.reduce(
7
+ (longestWidth, width) => Math.max(longestWidth, width),
8
+ MINIMUM_TOC_ARTICLE_WIDTH_PX,
9
+ );
10
+ }
@@ -0,0 +1,80 @@
1
+ import {
2
+ calculateTocMarkerPositions,
3
+ MAXIMUM_TOC_ITEM_COUNT,
4
+ } from './calculateTocMarkerPositions';
5
+ import type { ITocConnectorPoint } from './createTocConnectorPath';
6
+
7
+ const TOC_ARC_LEFT_PERCENT = 40.4;
8
+
9
+ export interface ICalculateFullTocArcPointsParams {
10
+ readonly width: number;
11
+ readonly height: number;
12
+ }
13
+
14
+ function formatCoordinate(value: number): number {
15
+ return Number(value.toFixed(2));
16
+ }
17
+
18
+ export function calculateFullTocArcPoints({
19
+ width,
20
+ height,
21
+ }: ICalculateFullTocArcPointsParams): readonly ITocConnectorPoint[] {
22
+ return calculateTocMarkerPositions(MAXIMUM_TOC_ITEM_COUNT).map(
23
+ ({ offsetFromMiddlePx, rightShiftPx, topPercent }) => {
24
+ return {
25
+ x:
26
+ width * (TOC_ARC_LEFT_PERCENT / 100) -
27
+ offsetFromMiddlePx +
28
+ rightShiftPx,
29
+ y: height * (topPercent / 100),
30
+ };
31
+ },
32
+ );
33
+ }
34
+
35
+ export function createTocArcPath(
36
+ points: readonly ITocConnectorPoint[],
37
+ ): string {
38
+ const firstPoint = points[0];
39
+
40
+ if (!firstPoint || points.length < 2) {
41
+ return '';
42
+ }
43
+
44
+ if (points.length === 2) {
45
+ const lastPoint = points[1];
46
+
47
+ return lastPoint
48
+ ? `M ${formatCoordinate(firstPoint.x)} ${formatCoordinate(firstPoint.y)} L ${formatCoordinate(lastPoint.x)} ${formatCoordinate(lastPoint.y)}`
49
+ : '';
50
+ }
51
+
52
+ const commands = [
53
+ `M ${formatCoordinate(firstPoint.x)} ${formatCoordinate(firstPoint.y)}`,
54
+ ];
55
+
56
+ for (let index = 0; index < points.length - 1; index += 1) {
57
+ const point = points[index];
58
+ const previousPoint = points[index - 1] ?? point;
59
+ const nextPoint = points[index + 1];
60
+ const followingPoint = points[index + 2] ?? nextPoint;
61
+
62
+ if (!point || !previousPoint || !nextPoint || !followingPoint) {
63
+ continue;
64
+ }
65
+
66
+ const firstControlPoint = {
67
+ x: point.x + (nextPoint.x - previousPoint.x) / 6,
68
+ y: point.y + (nextPoint.y - previousPoint.y) / 6,
69
+ };
70
+ const secondControlPoint = {
71
+ x: nextPoint.x - (followingPoint.x - point.x) / 6,
72
+ y: nextPoint.y - (followingPoint.y - point.y) / 6,
73
+ };
74
+ commands.push(
75
+ `C ${formatCoordinate(firstControlPoint.x)} ${formatCoordinate(firstControlPoint.y)} ${formatCoordinate(secondControlPoint.x)} ${formatCoordinate(secondControlPoint.y)} ${formatCoordinate(nextPoint.x)} ${formatCoordinate(nextPoint.y)}`,
76
+ );
77
+ }
78
+
79
+ return commands.join(' ');
80
+ }
@@ -0,0 +1,43 @@
1
+ export interface ITocConnectorPoint {
2
+ readonly x: number;
3
+ readonly y: number;
4
+ }
5
+
6
+ const CONNECTOR_SKEW_WIDTH_PX = 24;
7
+ const CONNECTOR_ARTICLE_OVERLAP_PX = 12;
8
+
9
+ export interface ICalculateTocConnectorEndXParams {
10
+ readonly articleLeftX: number;
11
+ }
12
+
13
+ function formatCoordinate(value: number): number {
14
+ return Number(value.toFixed(2));
15
+ }
16
+
17
+ export function calculateTocConnectorEndX({
18
+ articleLeftX,
19
+ }: ICalculateTocConnectorEndXParams): number {
20
+ return articleLeftX + CONNECTOR_ARTICLE_OVERLAP_PX;
21
+ }
22
+
23
+ export function createTocConnectorPath(
24
+ start: ITocConnectorPoint,
25
+ end: ITocConnectorPoint,
26
+ isMiddle: boolean,
27
+ ): string {
28
+ const startX = formatCoordinate(start.x);
29
+ const startY = formatCoordinate(start.y);
30
+ const endX = formatCoordinate(end.x);
31
+ const endY = formatCoordinate(end.y);
32
+
33
+ if (isMiddle) {
34
+ return `M ${startX} ${startY} L ${endX} ${endY}`;
35
+ }
36
+
37
+ const availableWidth = Math.max(0, end.x - start.x);
38
+ const elbowX = formatCoordinate(
39
+ start.x + Math.min(CONNECTOR_SKEW_WIDTH_PX, availableWidth / 2),
40
+ );
41
+
42
+ return `M ${startX} ${startY} L ${elbowX} ${endY} L ${endX} ${endY}`;
43
+ }
@@ -0,0 +1,5 @@
1
+ export * from './calculateTocCenterAlignmentOffset';
2
+ export * from './calculateTocMarkerPositions';
3
+ export * from './calculateUniformTocArticleWidth';
4
+ export * from './createTocArcPath';
5
+ export * from './createTocConnectorPath';