hexo-theme-stellar 1.42.1 → 1.43.1
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/.claude/skills/stellar-theme-dev/SKILL.md +1 -0
- package/CHANGELOG.md +41 -0
- package/THIRD-PARTY-NOTICES.md +28 -0
- package/_config.yml +17 -7
- package/_data/icons.yml +10 -9
- package/languages/en.yml +11 -0
- package/languages/zh-CN.yml +11 -0
- package/languages/zh-TW.yml +11 -0
- package/layout/_partial/comments/artalk/layout.ejs +1 -1
- package/layout/_partial/comments/beaudar/layout.ejs +1 -1
- package/layout/_partial/comments/giscus/layout.ejs +1 -1
- package/layout/_partial/comments/twikoo/layout.ejs +1 -1
- package/layout/_partial/comments/utterances/layout.ejs +1 -1
- package/layout/_partial/comments/waline/layout.ejs +1 -1
- package/layout/_partial/components/collection-item.ejs +44 -0
- package/layout/_partial/components/collection.ejs +26 -0
- package/layout/_partial/components/widget-frame.ejs +35 -0
- package/layout/_partial/cover/wiki_cover.ejs +82 -26
- package/layout/_partial/dropdown.ejs +62 -0
- package/layout/_partial/head.ejs +9 -0
- package/layout/_partial/main/navbar/breadcrumb/blog.ejs +1 -1
- package/layout/_partial/main/post_list/archive_item.ejs +12 -0
- package/layout/_partial/main/post_list/topic_card.ejs +21 -9
- package/layout/_partial/main/post_list/wiki_card.ejs +49 -10
- package/layout/_partial/scripts/defines.ejs +2 -8
- package/layout/_partial/scripts/lazyload.ejs +0 -3
- package/layout/_partial/scripts.ejs +1 -0
- package/layout/_partial/sidebar/index_leftbar.ejs +12 -2
- package/layout/_partial/sidebar/logo.ejs +1 -1
- package/layout/_partial/sidebar/menu.ejs +21 -23
- package/layout/_partial/widgets/components/linklist.ejs +23 -21
- package/layout/_partial/widgets/linklist.ejs +7 -12
- package/layout/_partial/widgets/recent.ejs +52 -41
- package/layout/_partial/widgets/related.ejs +47 -36
- package/layout/_partial/widgets/tagtree.ejs +70 -54
- package/layout/_partial/widgets/toc.ejs +37 -62
- package/layout/_partial/widgets/tree.ejs +38 -51
- package/layout/_plugins/mermaid.ejs +3 -4
- package/layout/archive.ejs +11 -20
- package/layout/collection-preview.ejs +60 -0
- package/layout/index_topic.ejs +12 -6
- package/layout/index_wiki.ejs +49 -3
- package/layout/layout.ejs +4 -3
- package/package.json +1 -1
- package/scripts/events/index.js +0 -2
- package/scripts/generators/stellar-icons.js +1 -1
- package/scripts/lib/subtitle.js +5 -1
- package/scripts/tags/index.js +1 -0
- package/scripts/tags/lib/about.js +1 -1
- package/scripts/tags/lib/albums.js +1 -1
- package/scripts/tags/lib/banner.js +1 -1
- package/scripts/tags/lib/dropdown.js +64 -0
- package/scripts/tags/lib/friends.js +1 -1
- package/scripts/tags/lib/gallery.js +3 -3
- package/scripts/tags/lib/image.js +1 -1
- package/scripts/tags/lib/posters.js +1 -1
- package/scripts/tags/lib/sites.js +8 -4
- package/source/css/_common/base.styl +4 -4
- package/source/css/_common/blockquote.styl +2 -2
- package/source/css/_common/dropdown.styl +132 -0
- package/source/css/_common/html.styl +1 -1
- package/source/css/_common/loading.styl +5 -2
- package/source/css/_common/title.styl +8 -8
- package/source/css/_components/collection.styl +188 -0
- package/source/css/_components/layout.styl +1 -1
- package/source/css/_components/list.styl +44 -46
- package/source/css/_components/md.styl +3 -3
- package/source/css/_components/page-transition.styl +22 -0
- package/source/css/_components/pages/archives.styl +19 -18
- package/source/css/_components/pages/article-indent.styl +2 -2
- package/source/css/_components/pages/article-story.styl +9 -5
- package/source/css/_components/pages/collection-preview.styl +68 -0
- package/source/css/_components/pages/notebook.styl +11 -9
- package/source/css/_components/partial/article-footer.styl +7 -4
- package/source/css/_components/partial/cover.styl +280 -1
- package/source/css/_components/partial/related.styl +10 -6
- package/source/css/_components/sidebar/footer.styl +66 -12
- package/source/css/_components/sidebar/nav-area.styl +6 -1
- package/source/css/_components/sidebar/search.styl +6 -2
- package/source/css/_components/tag-plugins/chat.styl +1 -1
- package/source/css/_components/tag-plugins/folders.styl +4 -4
- package/source/css/_components/tag-plugins/folding.styl +3 -3
- package/source/css/_components/tag-plugins/hashtag.styl +2 -3
- package/source/css/_components/tag-plugins/mdrender.styl +1 -1
- package/source/css/_components/tag-plugins/note.styl +4 -4
- package/source/css/_components/tag-plugins/poetry.styl +5 -5
- package/source/css/_components/tag-plugins/quot.styl +1 -2
- package/source/css/_components/tag-plugins/quote.styl +1 -1
- package/source/css/_components/tag-plugins/tabs.styl +2 -2
- package/source/css/_components/tag-plugins/timeline.styl +4 -4
- package/source/css/_components/widgets/markdown.styl +1 -14
- package/source/css/_components/widgets/timeline.styl +2 -2
- package/source/css/_components/widgets/toc.styl +22 -20
- package/source/css/_components/widgets/widgets.styl +13 -1
- package/source/css/_components/wiki-card.styl +209 -0
- package/source/css/_custom.styl +22 -20
- package/source/css/_plugins/lazyload.styl +8 -5
- package/source/css/comments/utterances.styl +2 -2
- package/source/css/plugins/mermaid.styl +31 -12
- package/source/js/icons.js +1 -1
- package/source/js/main.js +62 -6
- package/source/js/plugins/adaptive-text.js +46 -2
- package/source/js/plugins/dropdown.js +435 -0
- package/source/js/plugins/galaxy.js +423 -0
- package/source/js/search/algolia-search.js +1 -1
- package/source/js/search/local-search.js +1 -1
- package/source/js/services/ghinfo.js +31 -3
- package/source/js/services/siteinfo.js +26 -0
- package/source/js/services/sites.js +6 -2
- package/source/js/services.js +5 -2
- package/source/js/tagtree.js +7 -7
- package/source/js/utils.js +1 -1
- package/layout/_partial/widgets/components/link.ejs +0 -21
- package/source/css/_components/sidebar/menu.styl +0 -77
- package/source/css/_components/widgets/components.styl +0 -68
- package/source/css/_components/widgets/list.styl +0 -46
- package/source/css/_components/widgets/related.styl +0 -40
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// 文字自适应颜色 DOM 插件
|
|
2
|
-
// 扫描 [data-text-adaptive] 元素,解析背景来源(--cover-url → --bg-url →
|
|
2
|
+
// 扫描 [data-text-adaptive] 元素,解析背景来源(--adaptive-background → --cover-url → --bg-url →
|
|
3
3
|
// background-image → background-color),调用 stellar.color 计算文字颜色,
|
|
4
|
-
// 写入内联 --text-banner 与 --text-banner-theme
|
|
4
|
+
// 写入内联 --text-banner 与 --text-banner-theme;Wiki 封面另写入主题色蒙版变量。
|
|
5
5
|
// 属性值:
|
|
6
6
|
// theme(默认) 背景图平均色 lighten/darken(两种变量同色)
|
|
7
7
|
// contrast 黑白对比(两种变量同色)
|
|
@@ -19,6 +19,10 @@ function parseCssUrl(value) {
|
|
|
19
19
|
|
|
20
20
|
function resolveBackground(el) {
|
|
21
21
|
var style = window.getComputedStyle(el);
|
|
22
|
+
var adaptiveBackground = stellar.color.parse(style.getPropertyValue('--adaptive-background'));
|
|
23
|
+
if (adaptiveBackground) {
|
|
24
|
+
return { type: 'color', rgb: adaptiveBackground };
|
|
25
|
+
}
|
|
22
26
|
var cover = parseCssUrl(style.getPropertyValue('--cover-url'));
|
|
23
27
|
if (cover) {
|
|
24
28
|
return { type: 'image', url: cover };
|
|
@@ -85,6 +89,39 @@ function setSplitTextColors(el, rgb) {
|
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
91
|
|
|
92
|
+
// Wiki 卡片的 Today 风格内容层使用封面平均色:保留色相,增强弱饱和度后压低明度,
|
|
93
|
+
// 让满不透明蒙版下的白色文字始终可读。变量写到 .wiki-card:蒙版用深色版本,
|
|
94
|
+
// hover 边框使用同源但提高 20 个明度点的版本。
|
|
95
|
+
function setWikiOverlayColor(el, rgb) {
|
|
96
|
+
var node = el.parentElement;
|
|
97
|
+
while (node && node.nodeType === 1) {
|
|
98
|
+
if ((' ' + node.className + ' ').indexOf(' wiki-card ') !== -1) {
|
|
99
|
+
var themed = stellar.color.enhanceSaturation(rgb, { minSaturation: 0.45, boostBelow: 0.35 });
|
|
100
|
+
var overlay = stellar.color.darken(themed, 0.26);
|
|
101
|
+
var border = stellar.color.lighten(themed, 0.46);
|
|
102
|
+
if (overlay) {
|
|
103
|
+
node.style.setProperty('--wiki-overlay-color', overlay);
|
|
104
|
+
}
|
|
105
|
+
if (border) {
|
|
106
|
+
node.style.setProperty('--wiki-border-color', border);
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
node = node.parentElement;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Wiki 卡片主题色确定后(包括提取失败时的 CSS 回退)通知索引页。
|
|
115
|
+
// 覆盖层会同时等待原图 load,避免先用默认主题色绘制、再切换为平均色。
|
|
116
|
+
function notifyWikiOverlayReady(el) {
|
|
117
|
+
if (!el || !el.dispatchEvent || !document.createEvent) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
var event = document.createEvent('Event');
|
|
121
|
+
event.initEvent('wiki-overlay-ready', true, false);
|
|
122
|
+
el.dispatchEvent(event);
|
|
123
|
+
}
|
|
124
|
+
|
|
88
125
|
function applyToElement(el) {
|
|
89
126
|
// 用户显式覆盖优先:首次处理时元素已有内联 --text-banner 或内联 color 则跳过
|
|
90
127
|
// (插件自身写入的变量不视为用户覆盖,主题切换重算时需重新应用)
|
|
@@ -103,6 +140,10 @@ function applyToElement(el) {
|
|
|
103
140
|
} else {
|
|
104
141
|
setTextColor(el, styleName, rgb);
|
|
105
142
|
}
|
|
143
|
+
if (styleName === 'split' && el.parentElement && (' ' + el.parentElement.className + ' ').indexOf(' wiki-card-cover ') !== -1) {
|
|
144
|
+
setWikiOverlayColor(el, rgb);
|
|
145
|
+
notifyWikiOverlayReady(el);
|
|
146
|
+
}
|
|
106
147
|
appliedElements.add(el);
|
|
107
148
|
};
|
|
108
149
|
if (bg.type === 'color') {
|
|
@@ -112,6 +153,9 @@ function applyToElement(el) {
|
|
|
112
153
|
stellar.color.getAverageColor(bg.url, { background: backdrop }).then(function (rgb) {
|
|
113
154
|
if (rgb) {
|
|
114
155
|
apply(rgb);
|
|
156
|
+
} else if (styleName === 'split' && el.parentElement && (' ' + el.parentElement.className + ' ').indexOf(' wiki-card-cover ') !== -1) {
|
|
157
|
+
// 平均色提取失败时不写变量,保留 CSS 回退色;但仍须解除 Wiki 覆盖层等待。
|
|
158
|
+
notifyWikiOverlayReady(el);
|
|
115
159
|
}
|
|
116
160
|
});
|
|
117
161
|
}
|
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
// 通用 dropdown 浮层:把打开的菜单挂到 body 下,避免被任意祖先容器裁剪。
|
|
2
|
+
(() => {
|
|
3
|
+
const selector = 'details.dropdown'
|
|
4
|
+
const gap = 8
|
|
5
|
+
const viewportPadding = 8
|
|
6
|
+
let layer = null
|
|
7
|
+
let active = null
|
|
8
|
+
let frame = null
|
|
9
|
+
let menuId = 0
|
|
10
|
+
|
|
11
|
+
function getLayer() {
|
|
12
|
+
if (layer && document.documentElement.contains(layer)) {
|
|
13
|
+
return layer
|
|
14
|
+
}
|
|
15
|
+
layer = document.createElement('div')
|
|
16
|
+
layer.className = 'dropdown-layer'
|
|
17
|
+
document.body.appendChild(layer)
|
|
18
|
+
return layer
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getDirectChild(parent, childSelector) {
|
|
22
|
+
for (const child of parent.children) {
|
|
23
|
+
if (child.matches(childSelector)) {
|
|
24
|
+
return child
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getViewportSize() {
|
|
31
|
+
return {
|
|
32
|
+
width: document.documentElement.clientWidth || window.innerWidth,
|
|
33
|
+
height: document.documentElement.clientHeight || window.innerHeight
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getDirection(dropdown) {
|
|
38
|
+
const direction = dropdown.getAttribute('direction')
|
|
39
|
+
return direction === 'up' || direction === 'down' ? direction : 'auto'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getAlign(dropdown) {
|
|
43
|
+
const align = dropdown.getAttribute('align')
|
|
44
|
+
return align === 'left' || align === 'right' ? align : 'auto'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function setMenuId(dropdown, menu) {
|
|
48
|
+
if (!menu.id) {
|
|
49
|
+
menuId += 1
|
|
50
|
+
menu.id = `stellar-dropdown-menu-${menuId}`
|
|
51
|
+
}
|
|
52
|
+
const trigger = getDirectChild(dropdown, '.dropdown-trigger')
|
|
53
|
+
if (trigger) {
|
|
54
|
+
trigger.setAttribute('aria-controls', menu.id)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function clearMenuPosition(menu) {
|
|
59
|
+
menu.style.removeProperty('top')
|
|
60
|
+
menu.style.removeProperty('left')
|
|
61
|
+
menu.style.removeProperty('max-height')
|
|
62
|
+
menu.style.removeProperty('visibility')
|
|
63
|
+
menu.style.removeProperty('opacity')
|
|
64
|
+
menu.removeAttribute('data-placement')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function hideMenuBeforeOpen(dropdown) {
|
|
68
|
+
const menu = getDirectChild(dropdown, '.dropdown-menu')
|
|
69
|
+
if (!menu || menu.classList.contains('dropdown-menu-portal')) {
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
menu.style.visibility = 'hidden'
|
|
73
|
+
menu.style.opacity = '0'
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isPointInRect(point, rect) {
|
|
77
|
+
return point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.bottom
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isPointInPolygon(point, polygon) {
|
|
81
|
+
let inside = false
|
|
82
|
+
for (let index = 0, previous = polygon.length - 1; index < polygon.length; previous = index++) {
|
|
83
|
+
const currentPoint = polygon[index]
|
|
84
|
+
const previousPoint = polygon[previous]
|
|
85
|
+
const intersects = currentPoint.y > point.y !== previousPoint.y > point.y &&
|
|
86
|
+
point.x < (previousPoint.x - currentPoint.x) * (point.y - currentPoint.y) /
|
|
87
|
+
(previousPoint.y - currentPoint.y) + currentPoint.x
|
|
88
|
+
if (intersects) {
|
|
89
|
+
inside = !inside
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return inside
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function updateBridge(state, triggerRect, menuRect, placement) {
|
|
96
|
+
const polygon = placement === 'up'
|
|
97
|
+
? [
|
|
98
|
+
{ x: triggerRect.left, y: triggerRect.top },
|
|
99
|
+
{ x: menuRect.left, y: menuRect.bottom },
|
|
100
|
+
{ x: menuRect.right, y: menuRect.bottom },
|
|
101
|
+
{ x: triggerRect.right, y: triggerRect.top }
|
|
102
|
+
]
|
|
103
|
+
: [
|
|
104
|
+
{ x: triggerRect.left, y: triggerRect.bottom },
|
|
105
|
+
{ x: triggerRect.right, y: triggerRect.bottom },
|
|
106
|
+
{ x: menuRect.right, y: menuRect.top },
|
|
107
|
+
{ x: menuRect.left, y: menuRect.top }
|
|
108
|
+
]
|
|
109
|
+
const clipPath = `polygon(${polygon.map(point => `${Math.round(point.x)}px ${Math.round(point.y)}px`).join(', ')})`
|
|
110
|
+
state.bridge.__stellarDropdownPolygon = polygon
|
|
111
|
+
state.bridge.style.clipPath = clipPath
|
|
112
|
+
state.bridge.style.webkitClipPath = clipPath
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isPointerInsideState(state, event) {
|
|
116
|
+
const target = event.target
|
|
117
|
+
if (target && (state.dropdown.contains(target) || state.menu.contains(target) || state.bridge.contains(target))) {
|
|
118
|
+
return true
|
|
119
|
+
}
|
|
120
|
+
const point = { x: event.clientX, y: event.clientY }
|
|
121
|
+
if (isPointInRect(point, state.trigger.getBoundingClientRect()) || isPointInRect(point, state.menu.getBoundingClientRect())) {
|
|
122
|
+
return true
|
|
123
|
+
}
|
|
124
|
+
return isPointInPolygon(point, state.bridge.__stellarDropdownPolygon || [])
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function closeState(state, focusTrigger) {
|
|
128
|
+
if (!state || active !== state) {
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
active = null
|
|
132
|
+
if (state.dropdown.open) {
|
|
133
|
+
state.dropdown.open = false
|
|
134
|
+
}
|
|
135
|
+
if (state.bridge.parentNode) {
|
|
136
|
+
state.bridge.remove()
|
|
137
|
+
}
|
|
138
|
+
state.menu.classList.remove('dropdown-menu-portal')
|
|
139
|
+
state.menu.classList.remove('dropdown-menu-visible')
|
|
140
|
+
state.menu.removeAttribute('data-portal')
|
|
141
|
+
clearMenuPosition(state.menu)
|
|
142
|
+
if (state.placeholder.parentNode) {
|
|
143
|
+
state.placeholder.parentNode.insertBefore(state.menu, state.placeholder)
|
|
144
|
+
state.placeholder.remove()
|
|
145
|
+
} else if (state.menu.parentNode) {
|
|
146
|
+
state.menu.remove()
|
|
147
|
+
}
|
|
148
|
+
state.dropdown.removeAttribute('data-dropdown-open')
|
|
149
|
+
if (focusTrigger && document.documentElement.contains(state.trigger)) {
|
|
150
|
+
state.trigger.focus()
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function chooseVertical(dropdown, menuHeight, topSpace, bottomSpace) {
|
|
155
|
+
const direction = getDirection(dropdown)
|
|
156
|
+
if (direction === 'up') {
|
|
157
|
+
return 'up'
|
|
158
|
+
}
|
|
159
|
+
if (direction === 'down') {
|
|
160
|
+
return 'down'
|
|
161
|
+
}
|
|
162
|
+
if (menuHeight <= bottomSpace) {
|
|
163
|
+
return 'down'
|
|
164
|
+
}
|
|
165
|
+
if (menuHeight <= topSpace) {
|
|
166
|
+
return 'up'
|
|
167
|
+
}
|
|
168
|
+
return topSpace >= bottomSpace ? 'up' : 'down'
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function chooseHorizontal(dropdown, rect, menuWidth, viewportWidth) {
|
|
172
|
+
const align = getAlign(dropdown)
|
|
173
|
+
const minLeft = viewportPadding
|
|
174
|
+
const maxLeft = Math.max(minLeft, viewportWidth - viewportPadding - menuWidth)
|
|
175
|
+
if (align === 'left') {
|
|
176
|
+
return Math.min(Math.max(rect.left, minLeft), maxLeft)
|
|
177
|
+
}
|
|
178
|
+
if (align === 'right') {
|
|
179
|
+
return Math.min(Math.max(rect.right - menuWidth, minLeft), maxLeft)
|
|
180
|
+
}
|
|
181
|
+
const leftAlignedLeft = rect.left
|
|
182
|
+
const rightAlignedLeft = rect.right - menuWidth
|
|
183
|
+
const leftFits = leftAlignedLeft >= minLeft && leftAlignedLeft <= maxLeft
|
|
184
|
+
const rightFits = rightAlignedLeft >= minLeft && rightAlignedLeft <= maxLeft
|
|
185
|
+
if (leftFits && rightFits) {
|
|
186
|
+
const rightSpace = viewportWidth - viewportPadding - rect.left
|
|
187
|
+
const leftSpace = rect.right - viewportPadding
|
|
188
|
+
return rightSpace >= leftSpace ? leftAlignedLeft : rightAlignedLeft
|
|
189
|
+
}
|
|
190
|
+
if (leftFits) {
|
|
191
|
+
return leftAlignedLeft
|
|
192
|
+
}
|
|
193
|
+
if (rightFits) {
|
|
194
|
+
return rightAlignedLeft
|
|
195
|
+
}
|
|
196
|
+
const preferLeft = viewportWidth - viewportPadding - rect.left >= rect.right - viewportPadding
|
|
197
|
+
const preferredLeft = preferLeft ? leftAlignedLeft : rightAlignedLeft
|
|
198
|
+
return Math.min(Math.max(preferredLeft, minLeft), maxLeft)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function positionActive() {
|
|
202
|
+
if (!active) {
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
const state = active
|
|
206
|
+
if (!document.documentElement.contains(state.trigger)) {
|
|
207
|
+
closeState(state, false)
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
const rect = state.trigger.getBoundingClientRect()
|
|
211
|
+
const viewport = getViewportSize()
|
|
212
|
+
if (rect.bottom <= 0 || rect.top >= viewport.height) {
|
|
213
|
+
closeState(state, false)
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const topSpace = Math.max(0, rect.top - gap - viewportPadding)
|
|
218
|
+
const bottomSpace = Math.max(0, viewport.height - rect.bottom - gap - viewportPadding)
|
|
219
|
+
const menu = state.menu
|
|
220
|
+
menu.style.visibility = 'hidden'
|
|
221
|
+
menu.style.maxHeight = 'none'
|
|
222
|
+
menu.style.top = '0px'
|
|
223
|
+
menu.style.left = '0px'
|
|
224
|
+
const naturalRect = menu.getBoundingClientRect()
|
|
225
|
+
const vertical = chooseVertical(state.dropdown, naturalRect.height, topSpace, bottomSpace)
|
|
226
|
+
const availableHeight = vertical === 'up' ? topSpace : bottomSpace
|
|
227
|
+
menu.style.maxHeight = `${Math.max(1, Math.floor(availableHeight))}px`
|
|
228
|
+
|
|
229
|
+
const menuRect = menu.getBoundingClientRect()
|
|
230
|
+
const topLimit = viewportPadding
|
|
231
|
+
const bottomLimit = Math.max(topLimit, viewport.height - viewportPadding - menuRect.height)
|
|
232
|
+
const preferredTop = vertical === 'up'
|
|
233
|
+
? rect.top - gap - menuRect.height
|
|
234
|
+
: rect.bottom + gap
|
|
235
|
+
const top = Math.min(Math.max(preferredTop, topLimit), bottomLimit)
|
|
236
|
+
const left = chooseHorizontal(state.dropdown, rect, menuRect.width, viewport.width)
|
|
237
|
+
menu.style.top = `${Math.round(top)}px`
|
|
238
|
+
menu.style.left = `${Math.round(left)}px`
|
|
239
|
+
menu.setAttribute('data-placement', vertical)
|
|
240
|
+
menu.style.removeProperty('visibility')
|
|
241
|
+
menu.style.removeProperty('opacity')
|
|
242
|
+
updateBridge(state, rect, menu.getBoundingClientRect(), vertical)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function schedulePosition() {
|
|
246
|
+
if (!active || frame !== null) {
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
frame = window.requestAnimationFrame(() => {
|
|
250
|
+
frame = null
|
|
251
|
+
positionActive()
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function openDropdown(dropdown) {
|
|
256
|
+
const menu = getDirectChild(dropdown, '.dropdown-menu')
|
|
257
|
+
const trigger = getDirectChild(dropdown, '.dropdown-trigger')
|
|
258
|
+
if (!menu || !trigger) {
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
if (active && active.dropdown !== dropdown) {
|
|
262
|
+
closeState(active, false)
|
|
263
|
+
}
|
|
264
|
+
if (active && active.dropdown === dropdown) {
|
|
265
|
+
schedulePosition()
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const placeholder = document.createComment('dropdown-menu')
|
|
270
|
+
const bridge = document.createElement('div')
|
|
271
|
+
bridge.className = 'dropdown-bridge'
|
|
272
|
+
if (!window.CSS || typeof window.CSS.supports !== 'function' ||
|
|
273
|
+
(!window.CSS.supports('clip-path', 'polygon(0 0, 1px 1px, 2px 2px)') &&
|
|
274
|
+
!window.CSS.supports('-webkit-clip-path', 'polygon(0 0, 1px 1px, 2px 2px)'))) {
|
|
275
|
+
bridge.style.pointerEvents = 'none'
|
|
276
|
+
}
|
|
277
|
+
menu.style.visibility = 'hidden'
|
|
278
|
+
menu.style.opacity = '0'
|
|
279
|
+
menu.parentNode.insertBefore(placeholder, menu)
|
|
280
|
+
const dropdownLayer = getLayer()
|
|
281
|
+
dropdownLayer.appendChild(bridge)
|
|
282
|
+
dropdownLayer.appendChild(menu)
|
|
283
|
+
setMenuId(dropdown, menu)
|
|
284
|
+
menu.classList.add('dropdown-menu-portal')
|
|
285
|
+
menu.setAttribute('data-portal', 'true')
|
|
286
|
+
dropdown.setAttribute('data-dropdown-open', 'true')
|
|
287
|
+
active = {
|
|
288
|
+
dropdown,
|
|
289
|
+
menu,
|
|
290
|
+
trigger,
|
|
291
|
+
placeholder,
|
|
292
|
+
bridge,
|
|
293
|
+
hoverMode: dropdown.__stellarDropdownHovering
|
|
294
|
+
}
|
|
295
|
+
positionActive()
|
|
296
|
+
if (!dropdown.open) {
|
|
297
|
+
dropdown.open = true
|
|
298
|
+
}
|
|
299
|
+
window.requestAnimationFrame(() => {
|
|
300
|
+
if (active && active.dropdown === dropdown) {
|
|
301
|
+
menu.classList.add('dropdown-menu-visible')
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function bind(dropdown) {
|
|
307
|
+
if (dropdown.__stellarDropdownBound) {
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
dropdown.__stellarDropdownBound = true
|
|
311
|
+
dropdown.addEventListener('mouseenter', () => {
|
|
312
|
+
dropdown.__stellarDropdownHovering = true
|
|
313
|
+
if (active && active.dropdown === dropdown) {
|
|
314
|
+
active.hoverMode = true
|
|
315
|
+
}
|
|
316
|
+
if (!active || active.dropdown !== dropdown) {
|
|
317
|
+
openDropdown(dropdown)
|
|
318
|
+
}
|
|
319
|
+
})
|
|
320
|
+
const trigger = getDirectChild(dropdown, '.dropdown-trigger')
|
|
321
|
+
if (trigger) {
|
|
322
|
+
trigger.addEventListener('click', event => {
|
|
323
|
+
event.preventDefault()
|
|
324
|
+
if (!active || active.dropdown !== dropdown) {
|
|
325
|
+
openDropdown(dropdown)
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
trigger.addEventListener('keydown', event => {
|
|
329
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
330
|
+
event.preventDefault()
|
|
331
|
+
if (active && active.dropdown === dropdown) {
|
|
332
|
+
closeState(active, false)
|
|
333
|
+
} else {
|
|
334
|
+
openDropdown(dropdown)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
})
|
|
338
|
+
}
|
|
339
|
+
dropdown.addEventListener('mouseleave', () => {
|
|
340
|
+
dropdown.__stellarDropdownHovering = false
|
|
341
|
+
})
|
|
342
|
+
dropdown.addEventListener('toggle', () => {
|
|
343
|
+
if (dropdown.open) {
|
|
344
|
+
if (!active || active.dropdown !== dropdown) {
|
|
345
|
+
hideMenuBeforeOpen(dropdown)
|
|
346
|
+
openDropdown(dropdown)
|
|
347
|
+
} else {
|
|
348
|
+
schedulePosition()
|
|
349
|
+
}
|
|
350
|
+
} else if (active && active.dropdown === dropdown) {
|
|
351
|
+
closeState(active, false)
|
|
352
|
+
}
|
|
353
|
+
})
|
|
354
|
+
if (dropdown.open) {
|
|
355
|
+
openDropdown(dropdown)
|
|
356
|
+
}
|
|
357
|
+
dropdown.__stellarDropdownHovering = false
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function bindTree(root) {
|
|
361
|
+
if (root.nodeType !== 1) {
|
|
362
|
+
return
|
|
363
|
+
}
|
|
364
|
+
if (root.matches(selector)) {
|
|
365
|
+
bind(root)
|
|
366
|
+
}
|
|
367
|
+
root.querySelectorAll(selector).forEach(bind)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function init() {
|
|
371
|
+
if (!document.body || !window.MutationObserver) {
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
bindTree(document.body)
|
|
375
|
+
const observer = new MutationObserver(records => {
|
|
376
|
+
records.forEach(record => {
|
|
377
|
+
record.addedNodes.forEach(bindTree)
|
|
378
|
+
record.removedNodes.forEach(node => {
|
|
379
|
+
if (active && node.nodeType === 1 && (node === active.dropdown || node.contains(active.dropdown))) {
|
|
380
|
+
closeState(active, false)
|
|
381
|
+
}
|
|
382
|
+
})
|
|
383
|
+
})
|
|
384
|
+
})
|
|
385
|
+
observer.observe(document.body, { childList: true, subtree: true })
|
|
386
|
+
|
|
387
|
+
document.addEventListener('click', event => {
|
|
388
|
+
if (!active) {
|
|
389
|
+
return
|
|
390
|
+
}
|
|
391
|
+
if (active.dropdown.contains(event.target) || active.menu.contains(event.target)) {
|
|
392
|
+
return
|
|
393
|
+
}
|
|
394
|
+
closeState(active, false)
|
|
395
|
+
}, true)
|
|
396
|
+
|
|
397
|
+
document.addEventListener('mousemove', event => {
|
|
398
|
+
if (!active || !active.hoverMode || isPointerInsideState(active, event)) {
|
|
399
|
+
return
|
|
400
|
+
}
|
|
401
|
+
closeState(active, false)
|
|
402
|
+
}, true)
|
|
403
|
+
|
|
404
|
+
document.addEventListener('keydown', event => {
|
|
405
|
+
if (!active) {
|
|
406
|
+
return
|
|
407
|
+
}
|
|
408
|
+
if (event.key === 'Escape') {
|
|
409
|
+
event.preventDefault()
|
|
410
|
+
closeState(active, true)
|
|
411
|
+
return
|
|
412
|
+
}
|
|
413
|
+
if (event.key === 'Tab' && document.activeElement === active.trigger && !event.shiftKey) {
|
|
414
|
+
const firstItem = active.menu.querySelector('a, button, [tabindex]:not([tabindex="-1"])')
|
|
415
|
+
if (firstItem) {
|
|
416
|
+
event.preventDefault()
|
|
417
|
+
firstItem.focus()
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
window.addEventListener('resize', schedulePosition)
|
|
423
|
+
window.addEventListener('scroll', schedulePosition, true)
|
|
424
|
+
if (window.visualViewport) {
|
|
425
|
+
window.visualViewport.addEventListener('resize', schedulePosition)
|
|
426
|
+
window.visualViewport.addEventListener('scroll', schedulePosition)
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (document.readyState === 'loading') {
|
|
431
|
+
document.addEventListener('DOMContentLoaded', init, { once: true })
|
|
432
|
+
} else {
|
|
433
|
+
init()
|
|
434
|
+
}
|
|
435
|
+
})()
|