@tnotesjs/ui 0.4.1 → 0.4.2
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/dist/swiper/hydrate.d.ts +26 -0
- package/dist/swiper/hydrate.js +126 -0
- package/package.json +3 -2
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side tab activation for `.tn-swiper` shells emitted by the SSG
|
|
3
|
+
* markdown pipeline (empty `.tn-swiper-tabs`, all slides in the wrapper).
|
|
4
|
+
* Desk also uses this after building slide DOM so both hosts share one path.
|
|
5
|
+
*/
|
|
6
|
+
declare function applySwiperTabsPadding(tabs: HTMLElement, hasNav: boolean): void;
|
|
7
|
+
declare function createSwiperTabNav(handlers: {
|
|
8
|
+
onPrev: () => void;
|
|
9
|
+
onNext: () => void;
|
|
10
|
+
}): {
|
|
11
|
+
prev: HTMLButtonElement;
|
|
12
|
+
line: HTMLSpanElement;
|
|
13
|
+
next: HTMLButtonElement;
|
|
14
|
+
};
|
|
15
|
+
declare function wrapSlideIndex(index: number, length: number, delta: -1 | 1): number;
|
|
16
|
+
declare const SWIPER_EMPTY_TEXT = "empty swiper";
|
|
17
|
+
/** 没有图片幻灯片时的壳。Desk 与站点用同一段结构。 */
|
|
18
|
+
declare function emptySwiperMarkup(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Hydrate every `.tn-swiper` under `root` (or `document`). Idempotent:
|
|
21
|
+
* already-hydrated roots are skipped via `data-tn-swiper-ready`.
|
|
22
|
+
* When `root` itself is a `.tn-swiper`, it is included.
|
|
23
|
+
*/
|
|
24
|
+
declare function hydrateTnSwipers(root?: ParentNode): number;
|
|
25
|
+
|
|
26
|
+
export { SWIPER_EMPTY_TEXT, applySwiperTabsPadding, createSwiperTabNav, emptySwiperMarkup, hydrateTnSwipers, wrapSlideIndex };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// src/swiper/hydrate.ts
|
|
2
|
+
function applySwiperTabsPadding(tabs, hasNav) {
|
|
3
|
+
tabs.style.padding = hasNav ? "0 0.8rem 0 3rem" : "0 0.8rem";
|
|
4
|
+
}
|
|
5
|
+
function createSwiperTabNav(handlers) {
|
|
6
|
+
const prev = document.createElement("button");
|
|
7
|
+
prev.type = "button";
|
|
8
|
+
prev.className = "tn-tab-nav tn-tab-prev";
|
|
9
|
+
prev.textContent = "<";
|
|
10
|
+
prev.title = "\u4E0A\u4E00\u9875";
|
|
11
|
+
prev.addEventListener("click", (event) => {
|
|
12
|
+
event.preventDefault();
|
|
13
|
+
event.stopPropagation();
|
|
14
|
+
handlers.onPrev();
|
|
15
|
+
});
|
|
16
|
+
const line = document.createElement("span");
|
|
17
|
+
line.className = "tn-tab-nav tab-tab-line";
|
|
18
|
+
line.textContent = "/";
|
|
19
|
+
const next = document.createElement("button");
|
|
20
|
+
next.type = "button";
|
|
21
|
+
next.className = "tn-tab-nav tn-tab-next";
|
|
22
|
+
next.textContent = ">";
|
|
23
|
+
next.title = "\u4E0B\u4E00\u9875";
|
|
24
|
+
next.addEventListener("click", (event) => {
|
|
25
|
+
event.preventDefault();
|
|
26
|
+
event.stopPropagation();
|
|
27
|
+
handlers.onNext();
|
|
28
|
+
});
|
|
29
|
+
return { prev, line, next };
|
|
30
|
+
}
|
|
31
|
+
function wrapSlideIndex(index, length, delta) {
|
|
32
|
+
if (length <= 0) return 0;
|
|
33
|
+
return (index + delta + length) % length;
|
|
34
|
+
}
|
|
35
|
+
var SWIPER_EMPTY_TEXT = "empty swiper";
|
|
36
|
+
function emptySwiperMarkup() {
|
|
37
|
+
return `<div class="tn-swiper is-empty"><div class="swiper-container"><p class="tn-swiper-empty">${SWIPER_EMPTY_TEXT}</p></div></div>`;
|
|
38
|
+
}
|
|
39
|
+
var READY = "tnSwiperReady";
|
|
40
|
+
function hydrateTnSwipers(root = document) {
|
|
41
|
+
const hosts = [];
|
|
42
|
+
if (root instanceof HTMLElement && root.classList.contains("tn-swiper")) {
|
|
43
|
+
hosts.push(root);
|
|
44
|
+
}
|
|
45
|
+
hosts.push(...root.querySelectorAll(".tn-swiper"));
|
|
46
|
+
let count = 0;
|
|
47
|
+
const seen = /* @__PURE__ */ new Set();
|
|
48
|
+
for (const host of hosts) {
|
|
49
|
+
if (seen.has(host)) continue;
|
|
50
|
+
seen.add(host);
|
|
51
|
+
if (host.dataset[READY] === "true") continue;
|
|
52
|
+
if (hydrateOne(host)) {
|
|
53
|
+
host.dataset[READY] = "true";
|
|
54
|
+
count += 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return count;
|
|
58
|
+
}
|
|
59
|
+
function hydrateOne(host) {
|
|
60
|
+
if (host.classList.contains("is-empty")) return true;
|
|
61
|
+
const tabs = host.querySelector(".tn-swiper-tabs");
|
|
62
|
+
const wrapper = host.querySelector(".swiper-wrapper");
|
|
63
|
+
if (!wrapper) return false;
|
|
64
|
+
const slides = [...wrapper.querySelectorAll(":scope > .swiper-slide")];
|
|
65
|
+
if (slides.length === 0) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
let tabsEl = tabs;
|
|
69
|
+
if (!tabsEl) {
|
|
70
|
+
tabsEl = document.createElement("div");
|
|
71
|
+
tabsEl.className = "tn-swiper-tabs";
|
|
72
|
+
host.prepend(tabsEl);
|
|
73
|
+
}
|
|
74
|
+
const useTabs = slides.length > 1;
|
|
75
|
+
tabsEl.replaceChildren();
|
|
76
|
+
applySwiperTabsPadding(tabsEl, useTabs);
|
|
77
|
+
const tabButtons = [];
|
|
78
|
+
const activate = (index) => {
|
|
79
|
+
tabButtons.forEach(
|
|
80
|
+
(button, buttonIndex) => button.classList.toggle("active", buttonIndex === index)
|
|
81
|
+
);
|
|
82
|
+
slides.forEach((slide, slideIndex) => {
|
|
83
|
+
const on = slideIndex === index;
|
|
84
|
+
slide.classList.toggle("is-active", on);
|
|
85
|
+
slide.hidden = !on;
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
const activeIndex = () => {
|
|
89
|
+
const found = tabButtons.findIndex((button) => button.classList.contains("active"));
|
|
90
|
+
return found >= 0 ? found : 0;
|
|
91
|
+
};
|
|
92
|
+
if (useTabs) {
|
|
93
|
+
const nav = createSwiperTabNav({
|
|
94
|
+
onPrev: () => activate(wrapSlideIndex(activeIndex(), slides.length, -1)),
|
|
95
|
+
onNext: () => activate(wrapSlideIndex(activeIndex(), slides.length, 1))
|
|
96
|
+
});
|
|
97
|
+
tabsEl.append(nav.prev, nav.line);
|
|
98
|
+
slides.forEach((slide, index) => {
|
|
99
|
+
const title = slide.dataset.title?.trim() || slide.querySelector("img")?.getAttribute("alt")?.trim() || "img";
|
|
100
|
+
slide.dataset.title = title;
|
|
101
|
+
const tab = document.createElement("button");
|
|
102
|
+
tab.type = "button";
|
|
103
|
+
tab.className = "tn-tab";
|
|
104
|
+
tab.textContent = title;
|
|
105
|
+
tab.addEventListener("click", () => activate(index));
|
|
106
|
+
tabButtons.push(tab);
|
|
107
|
+
tabsEl.append(tab);
|
|
108
|
+
});
|
|
109
|
+
tabsEl.append(nav.next);
|
|
110
|
+
if (tabsEl.parentElement !== host || host.firstElementChild !== tabsEl) {
|
|
111
|
+
host.prepend(tabsEl);
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
tabsEl.remove();
|
|
115
|
+
}
|
|
116
|
+
activate(0);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
export {
|
|
120
|
+
SWIPER_EMPTY_TEXT,
|
|
121
|
+
applySwiperTabsPadding,
|
|
122
|
+
createSwiperTabNav,
|
|
123
|
+
emptySwiperMarkup,
|
|
124
|
+
hydrateTnSwipers,
|
|
125
|
+
wrapSlideIndex
|
|
126
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tnotesjs/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Shared Vue UI for TNotes built-in blocks — consumed by the TNotes SSG and Desk.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"./styles/swiper.css": "./src/styles/swiper.css",
|
|
34
34
|
"./swiper": {
|
|
35
35
|
"types": "./src/swiper/hydrate.ts",
|
|
36
|
+
"node": "./dist/swiper/hydrate.js",
|
|
36
37
|
"import": "./src/swiper/hydrate.ts",
|
|
37
38
|
"default": "./src/swiper/hydrate.ts"
|
|
38
39
|
},
|
|
@@ -126,7 +127,7 @@
|
|
|
126
127
|
],
|
|
127
128
|
"license": "MIT",
|
|
128
129
|
"scripts": {
|
|
129
|
-
"build": "tsup src/code/highlight.ts src/components/Footprints/parse.ts src/components/Mindmap/markdown.ts src/markdown/image.ts --format esm --dts --out-dir dist --clean",
|
|
130
|
+
"build": "tsup src/code/highlight.ts src/components/Footprints/parse.ts src/components/Mindmap/markdown.ts src/markdown/image.ts src/swiper/hydrate.ts --format esm --dts --out-dir dist --clean",
|
|
130
131
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
131
132
|
"test": "vitest run",
|
|
132
133
|
"lint": "eslint . --max-warnings=0",
|