cbvirtua 1.0.0 → 1.0.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/package.json +5 -2
- package/vue/ListItem.tsx +64 -0
- package/vue/VList.tsx +291 -0
- package/vue/ec.css +48 -0
- package/vue/ec.js +78 -0
- package/vue/l.less +69 -0
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cbvirtua",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
8
|
},
|
|
9
9
|
"author": "",
|
|
10
|
-
"license": "ISC"
|
|
10
|
+
"license": "ISC",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"cbvirtua": "^1.0.0"
|
|
13
|
+
}
|
|
11
14
|
}
|
package/vue/ListItem.tsx
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** @jsxImportSource vue */
|
|
2
|
+
import { ref, defineComponent, watch, StyleValue, PropType, VNode } from "vue";
|
|
3
|
+
import { ItemResizeObserver } from "../core/resizer";
|
|
4
|
+
import { isRTLDocument } from "../core/environment";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export const ListItem = /*#__PURE__*/ defineComponent({
|
|
10
|
+
props: {
|
|
11
|
+
_children: { type: Object as PropType<VNode>, required: true },
|
|
12
|
+
_resizer: { type: Object as PropType<ItemResizeObserver>, required: true },
|
|
13
|
+
_index: { type: Number, required: true },
|
|
14
|
+
_offset: { type: Number, required: true },
|
|
15
|
+
_hide: { type: Boolean },
|
|
16
|
+
_isHorizontal: { type: Boolean },
|
|
17
|
+
_element: { type: String, required: true },
|
|
18
|
+
},
|
|
19
|
+
setup(props) {
|
|
20
|
+
const elementRef = ref<HTMLDivElement>();
|
|
21
|
+
|
|
22
|
+
// The index may be changed if elements are inserted to or removed from the start of props.children
|
|
23
|
+
watch(
|
|
24
|
+
() => elementRef.value && props._index,
|
|
25
|
+
(_, __, onCleanup) => {
|
|
26
|
+
const cleanupObserver = props._resizer(elementRef.value!, props._index);
|
|
27
|
+
onCleanup(cleanupObserver);
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
flush: "post",
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return () => {
|
|
35
|
+
const {
|
|
36
|
+
_children: children,
|
|
37
|
+
_offset: offset,
|
|
38
|
+
_hide: hide,
|
|
39
|
+
_element: Element,
|
|
40
|
+
_isHorizontal: isHorizontal,
|
|
41
|
+
} = props as Omit<typeof props, "_element"> & { _element: "div" };
|
|
42
|
+
|
|
43
|
+
const style: StyleValue = {
|
|
44
|
+
margin: 0,
|
|
45
|
+
padding: 0,
|
|
46
|
+
position: "absolute",
|
|
47
|
+
[isHorizontal ? "height" : "width"]: "100%",
|
|
48
|
+
[isHorizontal ? "top" : "left"]: "0px",
|
|
49
|
+
[isHorizontal ? (isRTLDocument() ? "right" : "left") : "top"]:
|
|
50
|
+
offset + "px",
|
|
51
|
+
visibility: hide ? "hidden" : "visible",
|
|
52
|
+
};
|
|
53
|
+
if (isHorizontal) {
|
|
54
|
+
style.display = "flex";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Element ref={elementRef} style={style}>
|
|
59
|
+
{children}
|
|
60
|
+
</Element>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
});
|
package/vue/VList.tsx
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/** @jsxImportSource vue */
|
|
2
|
+
import {
|
|
3
|
+
ref,
|
|
4
|
+
onMounted,
|
|
5
|
+
defineComponent,
|
|
6
|
+
onUnmounted,
|
|
7
|
+
VNode,
|
|
8
|
+
watch,
|
|
9
|
+
ComponentOptionsMixin,
|
|
10
|
+
SlotsType,
|
|
11
|
+
ComponentOptionsWithObjectProps,
|
|
12
|
+
ComponentObjectPropsOptions,
|
|
13
|
+
} from "vue";
|
|
14
|
+
import {
|
|
15
|
+
SCROLL_IDLE,
|
|
16
|
+
UPDATE_SCROLL_STATE,
|
|
17
|
+
UPDATE_SCROLL_EVENT,
|
|
18
|
+
UPDATE_SCROLL_END_EVENT,
|
|
19
|
+
UPDATE_SIZE_STATE,
|
|
20
|
+
overscanEndIndex,
|
|
21
|
+
overscanStartIndex,
|
|
22
|
+
createVirtualStore,
|
|
23
|
+
ACTION_ITEMS_LENGTH_CHANGE,
|
|
24
|
+
getScrollSize,
|
|
25
|
+
getMinContainerSize,
|
|
26
|
+
} from "../core/store";
|
|
27
|
+
import { createResizer } from "../core/resizer";
|
|
28
|
+
import { createScroller } from "../core/scroller";
|
|
29
|
+
import { ScrollToIndexOpts } from "../core/types";
|
|
30
|
+
import { ListItem } from "./ListItem";
|
|
31
|
+
import { exists } from "../core/utils";
|
|
32
|
+
|
|
33
|
+
interface VListHandle {
|
|
34
|
+
/**
|
|
35
|
+
* Get current scrollTop or scrollLeft.
|
|
36
|
+
*/
|
|
37
|
+
readonly scrollOffset: number;
|
|
38
|
+
/**
|
|
39
|
+
* Get current scrollHeight or scrollWidth.
|
|
40
|
+
*/
|
|
41
|
+
readonly scrollSize: number;
|
|
42
|
+
/**
|
|
43
|
+
* Get current offsetHeight or offsetWidth.
|
|
44
|
+
*/
|
|
45
|
+
readonly viewportSize: number;
|
|
46
|
+
/**
|
|
47
|
+
* Scroll to the item specified by index.
|
|
48
|
+
* @param index index of item
|
|
49
|
+
* @param opts options
|
|
50
|
+
*/
|
|
51
|
+
scrollToIndex(index: number, opts?: ScrollToIndexOpts): void;
|
|
52
|
+
/**
|
|
53
|
+
* Scroll to the given offset.
|
|
54
|
+
* @param offset offset from start
|
|
55
|
+
*/
|
|
56
|
+
scrollTo(offset: number): void;
|
|
57
|
+
/**
|
|
58
|
+
* Scroll by the given offset.
|
|
59
|
+
* @param offset offset from current position
|
|
60
|
+
*/
|
|
61
|
+
scrollBy(offset: number): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const props = {
|
|
65
|
+
/**
|
|
66
|
+
* The data items rendered by this component.
|
|
67
|
+
*/
|
|
68
|
+
data: { type: Array, required: true },
|
|
69
|
+
/**
|
|
70
|
+
* Number of items to render above/below the visible bounds of the list. You can increase to avoid showing blank items in fast scrolling.
|
|
71
|
+
* @defaultValue 4
|
|
72
|
+
*/
|
|
73
|
+
overscan: { type: Number, default: 4 },
|
|
74
|
+
/**
|
|
75
|
+
* Item size hint for unmeasured items. It will help to reduce scroll jump when items are measured if used properly.
|
|
76
|
+
*
|
|
77
|
+
* - If not set, initial item sizes will be automatically estimated from measured sizes. This is recommended for most cases.
|
|
78
|
+
* - If set, you can opt out estimation and use the value as initial item size.
|
|
79
|
+
*/
|
|
80
|
+
itemSize: Number,
|
|
81
|
+
/**
|
|
82
|
+
* While true is set, scroll position will be maintained from the end not usual start when items are added to/removed from start. It's recommended to set false if you add to/remove from mid/end of the list because it can cause unexpected behavior. This prop is useful for reverse infinite scrolling.
|
|
83
|
+
*/
|
|
84
|
+
shift: Boolean,
|
|
85
|
+
/**
|
|
86
|
+
* If true, rendered as a horizontally scrollable list. Otherwise rendered as a vertically scrollable list.
|
|
87
|
+
*/
|
|
88
|
+
horizontal: Boolean,
|
|
89
|
+
} satisfies ComponentObjectPropsOptions;
|
|
90
|
+
|
|
91
|
+
export const VList = /*#__PURE__*/ defineComponent({
|
|
92
|
+
props: props,
|
|
93
|
+
emits: ["scroll", "scrollEnd", "rangeChange"],
|
|
94
|
+
setup(props, { emit, expose, slots }) {
|
|
95
|
+
const isHorizontal = props.horizontal;
|
|
96
|
+
const rootRef = ref<HTMLDivElement>();
|
|
97
|
+
const store = createVirtualStore(
|
|
98
|
+
props.data.length,
|
|
99
|
+
props.itemSize ?? 40,
|
|
100
|
+
undefined,
|
|
101
|
+
undefined,
|
|
102
|
+
!props.itemSize
|
|
103
|
+
);
|
|
104
|
+
const resizer = createResizer(store, isHorizontal);
|
|
105
|
+
const scroller = createScroller(store, isHorizontal);
|
|
106
|
+
|
|
107
|
+
const rerender = ref(store._getStateVersion());
|
|
108
|
+
const unsubscribeStore = store._subscribe(
|
|
109
|
+
UPDATE_SCROLL_STATE + UPDATE_SIZE_STATE,
|
|
110
|
+
() => {
|
|
111
|
+
rerender.value = store._getStateVersion();
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const unsubscribeOnScroll = store._subscribe(UPDATE_SCROLL_EVENT, () => {
|
|
116
|
+
emit("scroll", store._getScrollOffset());
|
|
117
|
+
});
|
|
118
|
+
const unsubscribeOnScrollEnd = store._subscribe(
|
|
119
|
+
UPDATE_SCROLL_END_EVENT,
|
|
120
|
+
() => {
|
|
121
|
+
emit("scrollEnd");
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
onMounted(() => {
|
|
126
|
+
const root = rootRef.value;
|
|
127
|
+
if (!root) return;
|
|
128
|
+
resizer._observeRoot(root);
|
|
129
|
+
scroller._observe(root);
|
|
130
|
+
});
|
|
131
|
+
onUnmounted(() => {
|
|
132
|
+
unsubscribeStore();
|
|
133
|
+
unsubscribeOnScroll();
|
|
134
|
+
unsubscribeOnScrollEnd();
|
|
135
|
+
resizer._dispose();
|
|
136
|
+
scroller._dispose();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
watch(
|
|
140
|
+
() => props.data.length,
|
|
141
|
+
(count) => {
|
|
142
|
+
store._update(ACTION_ITEMS_LENGTH_CHANGE, [count, props.shift]);
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
watch(
|
|
147
|
+
[rerender, store._getJumpCount],
|
|
148
|
+
([, count], [, prevCount]) => {
|
|
149
|
+
if (count === prevCount) return;
|
|
150
|
+
|
|
151
|
+
const jump = store._flushJump();
|
|
152
|
+
if (!jump) return;
|
|
153
|
+
|
|
154
|
+
scroller._fixScrollJump(jump);
|
|
155
|
+
},
|
|
156
|
+
{ flush: "post" }
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
watch(
|
|
160
|
+
[rerender, store._getRange],
|
|
161
|
+
([, [start, end]], [, [prevStart, prevEnd]]) => {
|
|
162
|
+
if (prevStart === start && prevEnd === end) return;
|
|
163
|
+
|
|
164
|
+
emit("rangeChange", start, end);
|
|
165
|
+
},
|
|
166
|
+
{ flush: "post" }
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
expose({
|
|
170
|
+
get scrollOffset() {
|
|
171
|
+
return store._getScrollOffset();
|
|
172
|
+
},
|
|
173
|
+
get scrollSize() {
|
|
174
|
+
return getScrollSize(store);
|
|
175
|
+
},
|
|
176
|
+
get viewportSize() {
|
|
177
|
+
return store._getViewportSize();
|
|
178
|
+
},
|
|
179
|
+
scrollToIndex: scroller._scrollToIndex,
|
|
180
|
+
scrollTo: scroller._scrollTo,
|
|
181
|
+
scrollBy: scroller._scrollBy,
|
|
182
|
+
} satisfies VListHandle);
|
|
183
|
+
|
|
184
|
+
return () => {
|
|
185
|
+
rerender.value;
|
|
186
|
+
|
|
187
|
+
const count = props.data.length;
|
|
188
|
+
|
|
189
|
+
const [startIndex, endIndex] = store._getRange();
|
|
190
|
+
const scrollDirection = store._getScrollDirection();
|
|
191
|
+
const totalSize = store._getTotalSize();
|
|
192
|
+
|
|
193
|
+
// https://github.com/inokawa/virtua/issues/252#issuecomment-1822861368
|
|
194
|
+
const minSize = getMinContainerSize(store);
|
|
195
|
+
|
|
196
|
+
const items: VNode[] = [];
|
|
197
|
+
for (
|
|
198
|
+
let i = overscanStartIndex(startIndex, props.overscan, scrollDirection),
|
|
199
|
+
j = overscanEndIndex(
|
|
200
|
+
endIndex,
|
|
201
|
+
props.overscan,
|
|
202
|
+
scrollDirection,
|
|
203
|
+
count
|
|
204
|
+
);
|
|
205
|
+
i <= j;
|
|
206
|
+
i++
|
|
207
|
+
) {
|
|
208
|
+
const e = slots.default(props.data![i]!)[0]! as VNode;
|
|
209
|
+
const key = e.key;
|
|
210
|
+
items.push(
|
|
211
|
+
<ListItem
|
|
212
|
+
key={exists(key) ? key : "_" + i}
|
|
213
|
+
_resizer={resizer._observeItem}
|
|
214
|
+
_index={i}
|
|
215
|
+
_offset={store._getItemOffset(i)}
|
|
216
|
+
_hide={store._isUnmeasuredItem(i)}
|
|
217
|
+
_element="div"
|
|
218
|
+
_children={e}
|
|
219
|
+
_isHorizontal={isHorizontal}
|
|
220
|
+
/>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<div
|
|
226
|
+
ref={rootRef}
|
|
227
|
+
style={{
|
|
228
|
+
display: isHorizontal ? "inline-block" : "block",
|
|
229
|
+
[isHorizontal ? "overflowX" : "overflowY"]: "auto",
|
|
230
|
+
overflowAnchor: "none",
|
|
231
|
+
contain: "strict",
|
|
232
|
+
width: "100%",
|
|
233
|
+
height: "100%",
|
|
234
|
+
}}
|
|
235
|
+
>
|
|
236
|
+
<div
|
|
237
|
+
style={{
|
|
238
|
+
// contain: "content",
|
|
239
|
+
overflowAnchor: "none", // opt out browser's scroll anchoring because it will conflict to scroll anchoring of virtualizer
|
|
240
|
+
flex: "none", // flex style on parent can break layout
|
|
241
|
+
position: "relative",
|
|
242
|
+
visibility: "hidden",
|
|
243
|
+
width: isHorizontal ? totalSize + "px" : "100%",
|
|
244
|
+
height: isHorizontal ? "100%" : totalSize + "px",
|
|
245
|
+
[isHorizontal ? "minWidth" : "minHeight"]: minSize + "px",
|
|
246
|
+
pointerEvents: scrollDirection !== SCROLL_IDLE ? "none" : "auto",
|
|
247
|
+
}}
|
|
248
|
+
>
|
|
249
|
+
{items}
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
);
|
|
253
|
+
};
|
|
254
|
+
},
|
|
255
|
+
} as ComponentOptionsWithObjectProps<
|
|
256
|
+
typeof props,
|
|
257
|
+
VListHandle,
|
|
258
|
+
{},
|
|
259
|
+
{},
|
|
260
|
+
{},
|
|
261
|
+
ComponentOptionsMixin,
|
|
262
|
+
ComponentOptionsMixin,
|
|
263
|
+
{
|
|
264
|
+
/**
|
|
265
|
+
* Callback invoked whenever scroll offset changes.
|
|
266
|
+
* @param offset Current scrollTop or scrollLeft.
|
|
267
|
+
*/
|
|
268
|
+
scroll: (offset: number) => void;
|
|
269
|
+
/**
|
|
270
|
+
* Callback invoked when scrolling stops.
|
|
271
|
+
*/
|
|
272
|
+
scrollEnd: () => void;
|
|
273
|
+
/**
|
|
274
|
+
* Callback invoked when visible items range changes.
|
|
275
|
+
*/
|
|
276
|
+
rangeChange: (
|
|
277
|
+
/**
|
|
278
|
+
* The start index of viewable items.
|
|
279
|
+
*/
|
|
280
|
+
startIndex: number,
|
|
281
|
+
/**
|
|
282
|
+
* The end index of viewable items.
|
|
283
|
+
*/
|
|
284
|
+
endIndex: number
|
|
285
|
+
) => void;
|
|
286
|
+
},
|
|
287
|
+
string,
|
|
288
|
+
{},
|
|
289
|
+
string,
|
|
290
|
+
SlotsType<{ default: any }>
|
|
291
|
+
>);
|
package/vue/ec.css
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
.ellipsis-tooltip {
|
|
2
|
+
z-index: 10;
|
|
3
|
+
display: inline-block;
|
|
4
|
+
background: #333333;
|
|
5
|
+
color: #ffffff;
|
|
6
|
+
padding: 5px 10px;
|
|
7
|
+
font-size: 13px;
|
|
8
|
+
border-radius: 4px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.ellipsis-tooltip-arrow,
|
|
12
|
+
.ellipsis-tooltip-arrow::before {
|
|
13
|
+
position: absolute;
|
|
14
|
+
width: 6px;
|
|
15
|
+
height: 6px;
|
|
16
|
+
background: inherit;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.ellipsis-tooltip-arrow {
|
|
20
|
+
visibility: hidden;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.ellipsis-tooltip-arrow::before {
|
|
24
|
+
visibility: visible;
|
|
25
|
+
content: '';
|
|
26
|
+
transform: rotate(45deg);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.ellipsis-tooltip[data-popper-placement^='top'] > .ellipsis-tooltip-arrow {
|
|
30
|
+
bottom: -3px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.ellipsis-tooltip[data-popper-placement^='bottom'] > .ellipsis-tooltip-arrow {
|
|
34
|
+
top: -3px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.ellipsis-tooltip[data-popper-placement^='left'] > .ellipsis-tooltip-arrow {
|
|
38
|
+
right: -3px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.ellipsis-tooltip[data-popper-placement^='right'] > .ellipsis-tooltip-arrow {
|
|
42
|
+
left: -3px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
作者:JasonSubmara
|
|
46
|
+
链接:https://juejin.cn/post/7278301902659190821
|
|
47
|
+
来源:稀土掘金
|
|
48
|
+
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
|
package/vue/ec.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createPopper } from '@popperjs/core';
|
|
2
|
+
|
|
3
|
+
const getPadding = el => {
|
|
4
|
+
const style = window.getComputedStyle(el, null);
|
|
5
|
+
const paddingLeft = Number.parseInt(style.paddingLeft, 10) || 0;
|
|
6
|
+
const paddingRight = Number.parseInt(style.paddingRight, 10) || 0;
|
|
7
|
+
const paddingTop = Number.parseInt(style.paddingTop, 10) || 0;
|
|
8
|
+
const paddingBottom = Number.parseInt(style.paddingBottom, 10) || 0;
|
|
9
|
+
return {
|
|
10
|
+
left: paddingLeft,
|
|
11
|
+
right: paddingRight,
|
|
12
|
+
top: paddingTop,
|
|
13
|
+
bottom: paddingBottom,
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const renderContent = (target, parent) => {
|
|
18
|
+
const tooltipContent = document.createElement('div');
|
|
19
|
+
const arrowContent = document.createElement('div');
|
|
20
|
+
arrowContent.className = ['ellipsis-tooltip-arrow'].join(' ');
|
|
21
|
+
arrowContent.setAttribute('data-popper-arrow', 'true');
|
|
22
|
+
tooltipContent.innerText = target.dataset.title;
|
|
23
|
+
tooltipContent.setAttribute('role', 'tooltip');
|
|
24
|
+
tooltipContent.appendChild(arrowContent);
|
|
25
|
+
tooltipContent.className = ['ellipsis-tooltip'].join(' ');
|
|
26
|
+
parent.setAttribute('aria-describedby', 'tooltip');
|
|
27
|
+
parent.appendChild(tooltipContent);
|
|
28
|
+
return {
|
|
29
|
+
tooltipContent,
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function useEllipsisPopper(options = {}) {
|
|
34
|
+
const handleCellMouseEnter = event => {
|
|
35
|
+
const target = event.target;
|
|
36
|
+
const parent = target.parentNode;
|
|
37
|
+
let range = document.createRange();
|
|
38
|
+
range.setStart(target, 0);
|
|
39
|
+
range.setEnd(target, target.childNodes.length);
|
|
40
|
+
const rangeWidth = range.getBoundingClientRect().width;
|
|
41
|
+
range.detach();
|
|
42
|
+
const { left, right } = getPadding(target);
|
|
43
|
+
const horizontalPadding = left + right;
|
|
44
|
+
if (Math.floor(rangeWidth + horizontalPadding) > target.clientWidth) {
|
|
45
|
+
const { tooltipContent } = renderContent(target, parent);
|
|
46
|
+
const popperInstance = createPopper(parent, tooltipContent, {
|
|
47
|
+
placement: options.placement ?? 'top',
|
|
48
|
+
modifiers: [
|
|
49
|
+
{
|
|
50
|
+
name: 'offset',
|
|
51
|
+
options: {
|
|
52
|
+
offset: [0, 8],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const removePopper = () => {
|
|
59
|
+
popperInstance.destroy();
|
|
60
|
+
parent.removeChild(tooltipContent);
|
|
61
|
+
parent.removeAttribute('aria-describedby');
|
|
62
|
+
target.removeListener('mouseleave', removePopper);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
target.addEventListener('mouseleave', removePopper);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
handleCellMouseEnter,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
作者:JasonSubmara
|
|
76
|
+
链接:https://juejin.cn/post/7278301902659190821
|
|
77
|
+
来源:稀土掘金
|
|
78
|
+
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
|
package/vue/l.less
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#devices() {
|
|
2
|
+
.lenovo-1() {
|
|
3
|
+
@a: 1000px;
|
|
4
|
+
@b: 200px;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.rule(@content) {
|
|
9
|
+
@returns: #devices.lenovo-1();
|
|
10
|
+
@prop-1: @returns[@a];
|
|
11
|
+
@prop-2: @returns[@b];
|
|
12
|
+
|
|
13
|
+
@media (min-width: @prop-1) and (min-height: @prop-2) {
|
|
14
|
+
@content()
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.a {
|
|
19
|
+
|
|
20
|
+
.rule({
|
|
21
|
+
color:#000
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@devices: {
|
|
26
|
+
@lenovo: {
|
|
27
|
+
width: 1000px;
|
|
28
|
+
height: 800px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@huawei: {
|
|
32
|
+
width: 1200px;
|
|
33
|
+
height: 800px;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.adapt(@deviceName, @content) {
|
|
38
|
+
@width: @devices[@@deviceName][width];
|
|
39
|
+
@height: @devices[@@deviceName][width];
|
|
40
|
+
|
|
41
|
+
@media (min-width: width) and (min-height: @height) {
|
|
42
|
+
@content()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.v {
|
|
47
|
+
.adapt(lenovo, {
|
|
48
|
+
.c {
|
|
49
|
+
color:#000;
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// const { defineConfig } = require('@vue/cli-service')
|
|
55
|
+
// const path = require('node:path')
|
|
56
|
+
|
|
57
|
+
// module.exports = defineConfig({
|
|
58
|
+
// // 全局配置使用less变量
|
|
59
|
+
// pluginOptions: {
|
|
60
|
+
// 'style-resources-loader': {
|
|
61
|
+
// preProcessor: 'less',
|
|
62
|
+
// patterns: [
|
|
63
|
+
// // 这个是加上自己的路径,注意此处不能使用别名路径
|
|
64
|
+
// path.resolve(__dirname, "./src/assets/css/lessGlobalVars.less")
|
|
65
|
+
// ]
|
|
66
|
+
// }
|
|
67
|
+
// }
|
|
68
|
+
// })
|
|
69
|
+
|