sard 1.1.0 → 1.2.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/.DS_Store +0 -0
- package/README.md +1 -2
- package/all-components.js +2 -0
- package/components/action-sheet/action-sheet-item.vue.js +1 -1
- package/components/action-sheet/common.d.ts +1 -1
- package/components/button/button.vue.js +1 -1
- package/components/calendar-popout/common.d.ts +1 -1
- package/components/cascader-popout/common.d.ts +1 -1
- package/components/checkbox-popout/common.d.ts +1 -1
- package/components/color-picker-popout/common.d.ts +1 -1
- package/components/crop-image/common.d.ts +1 -1
- package/components/danmaku/index.css +1 -1
- package/components/datetime-picker-popout/common.d.ts +1 -1
- package/components/datetime-range-picker-popout/common.d.ts +1 -1
- package/components/descriptions/common.d.ts +8 -3
- package/components/descriptions/descriptions-item.vue.d.ts +0 -2
- package/components/descriptions/descriptions-item.vue.js +20 -21
- package/components/descriptions/descriptions.vue.js +56 -28
- package/components/descriptions/index.css +1 -1
- package/components/dialog/common.d.ts +1 -1
- package/components/dnd/dnd-item.vue.js +19 -9
- package/components/dnd/index.css +1 -1
- package/components/dropdown/common.d.ts +1 -1
- package/components/ellipsis/ellipsis.vue.js +62 -58
- package/components/ellipsis/index.css +1 -1
- package/components/image/image.vue.d.ts +1 -1
- package/components/index.d.ts +1 -0
- package/components/index.js +2 -1
- package/components/keyboard/keyboard.vue.d.ts +1 -1
- package/components/keyboard-popout/common.d.ts +1 -1
- package/components/keyboard-popout/keyboard-popout.vue.d.ts +1 -1
- package/components/list/index.css +1 -1
- package/components/load-more/load-more.vue.js +2 -2
- package/components/loading/loading.vue.js +1 -1
- package/components/motion/motion.vue.js +1 -1
- package/components/notify/common.d.ts +1 -1
- package/components/overlay/overlay.vue.js +6 -5
- package/components/picker/picker.vue.js +8 -5
- package/components/picker-popout/common.d.ts +1 -1
- package/components/picker-view/common.d.ts +22 -2
- package/components/picker-view/index.d.ts +2 -2
- package/components/picker-view/index.js +2 -2
- package/components/picker-view/picker-view-column.vue.d.ts +19 -0
- package/components/picker-view/picker-view-column.vue.js +167 -0
- package/components/picker-view/picker-view.vue.d.ts +55 -0
- package/components/picker-view/picker-view.vue.js +60 -0
- package/components/popout/common.d.ts +1 -1
- package/components/popout/popout.vue.js +2 -4
- package/components/popover/popover.vue.js +11 -10
- package/components/popup/popup.vue.js +1 -1
- package/components/preview-image/common.d.ts +1 -1
- package/components/preview-image/preview-image.vue.js +4 -4
- package/components/pull-down-refresh/pull-down-refresh.vue.js +4 -7
- package/components/radio-popout/common.d.ts +1 -1
- package/components/rate/rate.vue.js +2 -2
- package/components/select-popout/common.d.ts +1 -1
- package/components/share-sheet/common.d.ts +1 -1
- package/components/skeleton/skeleton.vue.js +1 -1
- package/components/swiper/index.d.ts +2 -2
- package/components/swiper/index.js +2 -2
- package/components/swiper/swiper-item.vue.d.ts +11 -0
- package/components/swiper/swiper-item.vue.js +32 -0
- package/components/swiper/swiper.vue.d.ts +147 -0
- package/components/swiper/swiper.vue.js +51 -0
- package/components/swiper/useSwiper.d.ts +1 -2
- package/components/swiper/useSwiper.js +12 -12
- package/components/table/table.vue.js +1 -2
- package/components/text-shrink/common.d.ts +28 -0
- package/components/text-shrink/common.js +8 -0
- package/components/text-shrink/index.css +1 -0
- package/components/text-shrink/index.d.ts +5 -0
- package/components/text-shrink/index.js +6 -0
- package/components/text-shrink/text-shrink.vue.d.ts +16 -0
- package/components/text-shrink/text-shrink.vue.js +252 -0
- package/components/toast/common.d.ts +1 -1
- package/global.d.ts +1 -0
- package/index.css +1 -1
- package/index.js +2 -2
- package/package.json +1 -1
- package/utils/createImperativeComponent.d.ts +12 -5
- package/utils/createImperativeComponent.js +13 -37
- package/utils/imperative/agent-runtime.d.ts +28 -0
- package/utils/imperative/agent-runtime.js +33 -0
- package/utils/imperative/imperative-agent.vue.d.ts +35 -0
- package/utils/imperative/imperative-agent.vue.js +60 -0
- package/utils/index.d.ts +0 -1
- package/utils/index.js +1 -2
- package/utils/is.d.ts +4 -1
- package/utils/is.js +5 -3
- package/utils/number.d.ts +1 -1
- package/utils/number.js +20 -19
- package/components/picker-view/picker-view-column.d.ts +0 -3
- package/components/picker-view/picker-view-column.js +0 -139
- package/components/picker-view/picker-view.d.ts +0 -21
- package/components/picker-view/picker-view.js +0 -57
- package/components/swiper/swiper-item.d.ts +0 -3
- package/components/swiper/swiper-item.js +0 -34
- package/components/swiper/swiper.d.ts +0 -57
- package/components/swiper/swiper.js +0 -43
- package/utils/vnode.d.ts +0 -9
- package/utils/vnode.js +0 -69
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { type SwiperSlots } from './common';
|
|
2
|
+
type __VLS_Slots = SwiperSlots;
|
|
3
|
+
declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
4
|
+
modelValue: {
|
|
5
|
+
type: NumberConstructor;
|
|
6
|
+
default: number;
|
|
7
|
+
};
|
|
8
|
+
showIndicator: {
|
|
9
|
+
type: BooleanConstructor;
|
|
10
|
+
default: boolean;
|
|
11
|
+
};
|
|
12
|
+
vertical: {
|
|
13
|
+
type: BooleanConstructor;
|
|
14
|
+
default: boolean;
|
|
15
|
+
};
|
|
16
|
+
speed: {
|
|
17
|
+
type: NumberConstructor;
|
|
18
|
+
default: number;
|
|
19
|
+
};
|
|
20
|
+
autoplay: {
|
|
21
|
+
type: BooleanConstructor;
|
|
22
|
+
default: boolean;
|
|
23
|
+
};
|
|
24
|
+
delay: {
|
|
25
|
+
type: NumberConstructor;
|
|
26
|
+
default: number;
|
|
27
|
+
};
|
|
28
|
+
loop: {
|
|
29
|
+
type: BooleanConstructor;
|
|
30
|
+
default: boolean;
|
|
31
|
+
};
|
|
32
|
+
slidesPerView: {
|
|
33
|
+
type: NumberConstructor;
|
|
34
|
+
default: number;
|
|
35
|
+
};
|
|
36
|
+
slidesPerGroup: {
|
|
37
|
+
type: NumberConstructor;
|
|
38
|
+
default: number;
|
|
39
|
+
};
|
|
40
|
+
spaceBetween: {
|
|
41
|
+
type: NumberConstructor;
|
|
42
|
+
default: number;
|
|
43
|
+
};
|
|
44
|
+
centeredSlides: {
|
|
45
|
+
type: BooleanConstructor;
|
|
46
|
+
default: boolean;
|
|
47
|
+
};
|
|
48
|
+
allowTouchMove: {
|
|
49
|
+
type: BooleanConstructor;
|
|
50
|
+
default: boolean;
|
|
51
|
+
};
|
|
52
|
+
autoHeight: {
|
|
53
|
+
type: BooleanConstructor;
|
|
54
|
+
default: boolean;
|
|
55
|
+
};
|
|
56
|
+
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
57
|
+
change: (value: number) => any;
|
|
58
|
+
transition: (event: {
|
|
59
|
+
dx: number;
|
|
60
|
+
dy: number;
|
|
61
|
+
}) => any;
|
|
62
|
+
"update:modelValue": (value: number) => any;
|
|
63
|
+
animationfinish: (value: number) => any;
|
|
64
|
+
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
65
|
+
modelValue: {
|
|
66
|
+
type: NumberConstructor;
|
|
67
|
+
default: number;
|
|
68
|
+
};
|
|
69
|
+
showIndicator: {
|
|
70
|
+
type: BooleanConstructor;
|
|
71
|
+
default: boolean;
|
|
72
|
+
};
|
|
73
|
+
vertical: {
|
|
74
|
+
type: BooleanConstructor;
|
|
75
|
+
default: boolean;
|
|
76
|
+
};
|
|
77
|
+
speed: {
|
|
78
|
+
type: NumberConstructor;
|
|
79
|
+
default: number;
|
|
80
|
+
};
|
|
81
|
+
autoplay: {
|
|
82
|
+
type: BooleanConstructor;
|
|
83
|
+
default: boolean;
|
|
84
|
+
};
|
|
85
|
+
delay: {
|
|
86
|
+
type: NumberConstructor;
|
|
87
|
+
default: number;
|
|
88
|
+
};
|
|
89
|
+
loop: {
|
|
90
|
+
type: BooleanConstructor;
|
|
91
|
+
default: boolean;
|
|
92
|
+
};
|
|
93
|
+
slidesPerView: {
|
|
94
|
+
type: NumberConstructor;
|
|
95
|
+
default: number;
|
|
96
|
+
};
|
|
97
|
+
slidesPerGroup: {
|
|
98
|
+
type: NumberConstructor;
|
|
99
|
+
default: number;
|
|
100
|
+
};
|
|
101
|
+
spaceBetween: {
|
|
102
|
+
type: NumberConstructor;
|
|
103
|
+
default: number;
|
|
104
|
+
};
|
|
105
|
+
centeredSlides: {
|
|
106
|
+
type: BooleanConstructor;
|
|
107
|
+
default: boolean;
|
|
108
|
+
};
|
|
109
|
+
allowTouchMove: {
|
|
110
|
+
type: BooleanConstructor;
|
|
111
|
+
default: boolean;
|
|
112
|
+
};
|
|
113
|
+
autoHeight: {
|
|
114
|
+
type: BooleanConstructor;
|
|
115
|
+
default: boolean;
|
|
116
|
+
};
|
|
117
|
+
}>> & Readonly<{
|
|
118
|
+
onChange?: ((value: number) => any) | undefined;
|
|
119
|
+
onTransition?: ((event: {
|
|
120
|
+
dx: number;
|
|
121
|
+
dy: number;
|
|
122
|
+
}) => any) | undefined;
|
|
123
|
+
"onUpdate:modelValue"?: ((value: number) => any) | undefined;
|
|
124
|
+
onAnimationfinish?: ((value: number) => any) | undefined;
|
|
125
|
+
}>, {
|
|
126
|
+
vertical: boolean;
|
|
127
|
+
modelValue: number;
|
|
128
|
+
autoHeight: boolean;
|
|
129
|
+
speed: number;
|
|
130
|
+
loop: boolean;
|
|
131
|
+
showIndicator: boolean;
|
|
132
|
+
autoplay: boolean;
|
|
133
|
+
delay: number;
|
|
134
|
+
slidesPerView: number;
|
|
135
|
+
slidesPerGroup: number;
|
|
136
|
+
spaceBetween: number;
|
|
137
|
+
centeredSlides: boolean;
|
|
138
|
+
allowTouchMove: boolean;
|
|
139
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
140
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
141
|
+
declare const _default: typeof __VLS_export;
|
|
142
|
+
export default _default;
|
|
143
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
144
|
+
new (): {
|
|
145
|
+
$slots: S;
|
|
146
|
+
};
|
|
147
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createBem } from "../../utils/bem.js";
|
|
2
|
+
import { swiperProps } from "./common.js";
|
|
3
|
+
import { useSwiper } from "./useSwiper.js";
|
|
4
|
+
import { Fragment, createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, openBlock, renderList, renderSlot, unref } from "vue";
|
|
5
|
+
//#region packages/sard/components/swiper/swiper.vue
|
|
6
|
+
var swiper_default = /*@__PURE__*/ defineComponent({
|
|
7
|
+
__name: "swiper",
|
|
8
|
+
props: swiperProps,
|
|
9
|
+
emits: [
|
|
10
|
+
"change",
|
|
11
|
+
"transition",
|
|
12
|
+
"animationfinish",
|
|
13
|
+
"update:modelValue"
|
|
14
|
+
],
|
|
15
|
+
setup(__props, { emit: __emit }) {
|
|
16
|
+
const props = __props;
|
|
17
|
+
const emit = __emit;
|
|
18
|
+
const bem = createBem("swiper");
|
|
19
|
+
const { rootRef, wrapperRef, activeIndex, pageCount, onTouchStart, onTouchMove, onTouchEnd, onPointerDown } = useSwiper(props, emit);
|
|
20
|
+
return (_ctx, _cache) => {
|
|
21
|
+
return openBlock(), createElementBlock("div", {
|
|
22
|
+
ref_key: "rootRef",
|
|
23
|
+
ref: rootRef,
|
|
24
|
+
class: normalizeClass([
|
|
25
|
+
unref(bem).b(),
|
|
26
|
+
unref(bem).m("vertical", _ctx.vertical),
|
|
27
|
+
unref(bem).is("auto-height", _ctx.autoHeight)
|
|
28
|
+
]),
|
|
29
|
+
onTouchstart: _cache[0] || (_cache[0] = (...args) => unref(onTouchStart) && unref(onTouchStart)(...args)),
|
|
30
|
+
onTouchmove: _cache[1] || (_cache[1] = (...args) => unref(onTouchMove) && unref(onTouchMove)(...args)),
|
|
31
|
+
onTouchend: _cache[2] || (_cache[2] = (...args) => unref(onTouchEnd) && unref(onTouchEnd)(...args)),
|
|
32
|
+
onTouchcancel: _cache[3] || (_cache[3] = (...args) => unref(onTouchEnd) && unref(onTouchEnd)(...args)),
|
|
33
|
+
onPointerdown: _cache[4] || (_cache[4] = (...args) => unref(onPointerDown) && unref(onPointerDown)(...args))
|
|
34
|
+
}, [createElementVNode("div", {
|
|
35
|
+
ref_key: "wrapperRef",
|
|
36
|
+
ref: wrapperRef,
|
|
37
|
+
class: normalizeClass([unref(bem).e("wrapper"), unref(bem).is("vertical", _ctx.vertical)])
|
|
38
|
+
}, [renderSlot(_ctx.$slots, "default")], 2), _ctx.showIndicator ? (openBlock(), createElementBlock("div", {
|
|
39
|
+
key: 0,
|
|
40
|
+
class: normalizeClass([unref(bem).e("dots"), unref(bem).is("vertical", _ctx.vertical)])
|
|
41
|
+
}, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(pageCount), (index) => {
|
|
42
|
+
return openBlock(), createElementBlock("div", {
|
|
43
|
+
key: index,
|
|
44
|
+
class: normalizeClass([unref(bem).e("dot"), unref(bem).is("active", unref(activeIndex) === index - 1)])
|
|
45
|
+
}, null, 2);
|
|
46
|
+
}), 128))], 2)) : createCommentVNode("v-if", true)], 34);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
//#endregion
|
|
51
|
+
export { swiper_default as default };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Ref
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
2
|
import { type SwiperSlideData } from './useSwiperData';
|
|
3
3
|
import type { SwiperEmits, SwiperPrivateProps } from './common';
|
|
4
4
|
type SwiperMember = SwiperSlideData;
|
|
@@ -15,7 +15,6 @@ export declare function useSwiper(props: SwiperPrivateProps, emit: SwiperEmits):
|
|
|
15
15
|
members: import("vue").ShallowRef<SwiperSlideData[], SwiperSlideData[]>;
|
|
16
16
|
activeIndex: Ref<number, number>;
|
|
17
17
|
pageCount: import("vue").ComputedRef<number>;
|
|
18
|
-
setSwiperItems: (_swiperItems: VNode[]) => void;
|
|
19
18
|
onTouchStart: (event: TouchEvent) => void;
|
|
20
19
|
onTouchMove: (event: TouchEvent) => void;
|
|
21
20
|
onTouchEnd: () => void;
|
|
@@ -17,17 +17,21 @@ function useSwiper(props, emit) {
|
|
|
17
17
|
const rootRef = ref(null);
|
|
18
18
|
const wrapperRef = ref(null);
|
|
19
19
|
const isRtl = useRtl();
|
|
20
|
-
let swiperItems = [];
|
|
21
20
|
const members = shallowRef([]);
|
|
22
21
|
function updateMembers(rawMembers) {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
const uniqueMembers = [];
|
|
23
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24
|
+
for (const member of rawMembers) {
|
|
25
|
+
const el = member.el;
|
|
26
|
+
if (el && !seen.has(el)) {
|
|
27
|
+
seen.add(el);
|
|
28
|
+
uniqueMembers.push(member);
|
|
29
|
+
}
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
uniqueMembers.sort((a, b) => {
|
|
32
|
+
return a.el.compareDocumentPosition(b.el) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
|
|
33
|
+
});
|
|
34
|
+
members.value = uniqueMembers;
|
|
31
35
|
}
|
|
32
36
|
provide(swiperContextKey, {
|
|
33
37
|
autoHeight: toRef(() => props.autoHeight),
|
|
@@ -40,9 +44,6 @@ function useSwiper(props, emit) {
|
|
|
40
44
|
updateMembers(members.value.filter((m) => m !== member));
|
|
41
45
|
}
|
|
42
46
|
});
|
|
43
|
-
const setSwiperItems = (_swiperItems) => {
|
|
44
|
-
swiperItems = _swiperItems;
|
|
45
|
-
};
|
|
46
47
|
const { offset, swiperSize, startEdge, endEdge, activeIndex, slidesPerView, slidesPerGroup, spaceBetween, centeredSlides, pageCount, loop, slides, setSlides, getNearestOffset, getPreviousNearestOffset, getNextNearestOffset, getNextIndexOffset, normalizeLoopOffset, snapTo } = useSwiperData();
|
|
47
48
|
watchSyncEffect(() => {
|
|
48
49
|
slidesPerView.value = props.slidesPerView;
|
|
@@ -159,7 +160,6 @@ function useSwiper(props, emit) {
|
|
|
159
160
|
members,
|
|
160
161
|
activeIndex,
|
|
161
162
|
pageCount,
|
|
162
|
-
setSwiperItems,
|
|
163
163
|
onTouchStart,
|
|
164
164
|
onTouchMove,
|
|
165
165
|
onTouchEnd,
|
|
@@ -381,12 +381,11 @@ var table_default = /*@__PURE__*/ defineComponent({
|
|
|
381
381
|
style: normalizeStyle(getCellStyle(cell.row, cell.rowIndex, cell.column, cell.columnIndex)),
|
|
382
382
|
class: normalizeClass(getCellClassName(cell.row, cell.rowIndex, cell.column, cell.columnIndex))
|
|
383
383
|
}, [cell.column.slot && _ctx.$slots[cell.column.slot] ? renderSlot(_ctx.$slots, cell.column.slot, {
|
|
384
|
-
key: 0,
|
|
385
384
|
row: cell.row,
|
|
386
385
|
column: cell.column,
|
|
387
386
|
rowIndex: cell.rowIndex,
|
|
388
387
|
columnIndex: cell.columnIndex
|
|
389
|
-
}) : cell.column.formatter ? (openBlock(), createBlock(resolveDynamicComponent(() => cell.column.formatter({
|
|
388
|
+
}, void 0, void 0, 0) : cell.column.formatter ? (openBlock(), createBlock(resolveDynamicComponent(() => cell.column.formatter({
|
|
390
389
|
row: cell.row,
|
|
391
390
|
rowIndex: cell.rowIndex,
|
|
392
391
|
column: cell.column,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
|
+
import { type DefaultProps } from '../config';
|
|
3
|
+
export interface TextShrinkProps {
|
|
4
|
+
/** 要渲染的纯文本,不解析 HTML */
|
|
5
|
+
text?: string;
|
|
6
|
+
/** 允许渲染的最大行数,1 表示单行不换行 */
|
|
7
|
+
maxLines?: number;
|
|
8
|
+
/** 字号上界(px);不传时在首次适配时读取根元素 computed font-size 并固化 */
|
|
9
|
+
baseFontSize?: number;
|
|
10
|
+
/**
|
|
11
|
+
* 缩放下限(px),默认 1。
|
|
12
|
+
* 需要在小于等于该字号时才放得下时,不再继续缩小,停在该字号并显示省略号。
|
|
13
|
+
*/
|
|
14
|
+
minFontSize?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare const defaultTextShrinkProps: DefaultProps<TextShrinkProps>;
|
|
17
|
+
export interface TextShrinkSlots {
|
|
18
|
+
}
|
|
19
|
+
export interface TextShrinkEmits {
|
|
20
|
+
}
|
|
21
|
+
export interface TextShrinkExpose {
|
|
22
|
+
/** 当前生效的字号(px),首次适配完成前为 undefined */
|
|
23
|
+
fontSize: Ref<number | undefined>;
|
|
24
|
+
/** 是否已停在缩放下限仍溢出(此时按 maxLines 截断) */
|
|
25
|
+
overflow: Ref<boolean>;
|
|
26
|
+
/** 手动触发一次重新适配(在下一帧执行) */
|
|
27
|
+
refit: () => void;
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.s-text-shrink{position:relative;display:block;min-width:0}.s-text-shrink:is(.is-measuring){visibility:hidden}.s-text-shrink__text{position:relative;display:block;overflow:hidden;word-break:normal;overflow-wrap:break-word}.s-text-shrink__text--nowrap{white-space:nowrap;text-overflow:ellipsis}.s-text-shrink__text--clamp{display:-webkit-box;-webkit-box-orient:vertical}.s-text-shrink__measure{position:absolute;top:0;left:0;z-index:-1;box-sizing:border-box;width:100%;padding:inherit;overflow:visible;visibility:hidden;pointer-events:none;contain:layout style}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type EnhancedComponent } from '../../utils';
|
|
2
|
+
import _TextShrink from './text-shrink.vue';
|
|
3
|
+
export declare const TextShrink: EnhancedComponent<typeof _TextShrink>;
|
|
4
|
+
export default TextShrink;
|
|
5
|
+
export type { TextShrinkProps, TextShrinkSlots, TextShrinkEmits, TextShrinkExpose } from './common';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { enhanceComponent } from "../../utils/component.js";
|
|
2
|
+
import text_shrink_default from "./text-shrink.vue.js";
|
|
3
|
+
//#region packages/sard/components/text-shrink/index.ts
|
|
4
|
+
const TextShrink = enhanceComponent(text_shrink_default);
|
|
5
|
+
//#endregion
|
|
6
|
+
export { TextShrink, TextShrink as default };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type TextShrinkExpose, type TextShrinkProps, type TextShrinkSlots } from './common';
|
|
2
|
+
type __VLS_Slots = TextShrinkSlots;
|
|
3
|
+
declare const __VLS_base: import("vue").DefineComponent<TextShrinkProps, TextShrinkExpose, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<TextShrinkProps> & Readonly<{}>, {
|
|
4
|
+
text: string;
|
|
5
|
+
maxLines: number;
|
|
6
|
+
baseFontSize: number;
|
|
7
|
+
minFontSize: number;
|
|
8
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
9
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
10
|
+
declare const _default: typeof __VLS_export;
|
|
11
|
+
export default _default;
|
|
12
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
13
|
+
new (): {
|
|
14
|
+
$slots: S;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { createBem } from "../../utils/bem.js";
|
|
2
|
+
import { defaultTextShrinkProps } from "./common.js";
|
|
3
|
+
import { computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, defineComponent, mergeDefaults, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, toDisplayString, unref, useTemplateRef, watch } from "vue";
|
|
4
|
+
//#region packages/sard/components/text-shrink/text-shrink.vue
|
|
5
|
+
const _hoisted_1 = ["textContent"];
|
|
6
|
+
const MIN_FONT_SIZE = 1;
|
|
7
|
+
/** 二分搜索收敛精度(px) */
|
|
8
|
+
const SEARCH_EPSILON = .05;
|
|
9
|
+
/**
|
|
10
|
+
* 尺寸比较容差。
|
|
11
|
+
* 必须小于 1 个字号步进:单行渲染一旦溢出哪怕 0.5px,text-overflow 就会用省略号
|
|
12
|
+
* 吃掉末尾一整个字,视觉上像“根本没放下”。这里只留吸收浮点误差的余量。
|
|
13
|
+
*/
|
|
14
|
+
const SIZE_TOLERANCE = .05;
|
|
15
|
+
/** 宽度变化容差:小于它视为同一宽度,跳过重复适配 */
|
|
16
|
+
const WIDTH_TOLERANCE = .5;
|
|
17
|
+
/** 单行按宽度外推分割点的次数上限,之后回到二分保证收敛 */
|
|
18
|
+
const INTERPOLATION_LIMIT = 2;
|
|
19
|
+
/** 首次读到的 computed font-size 不可信时的兜底基准 */
|
|
20
|
+
const FALLBACK_BASE_FONT_SIZE = 16;
|
|
21
|
+
/** 复用同一个 Range,避免每次测量都新建对象 */
|
|
22
|
+
var text_shrink_default = /*@__PURE__*/ defineComponent({
|
|
23
|
+
__name: "text-shrink",
|
|
24
|
+
props: /*@__PURE__*/ mergeDefaults({
|
|
25
|
+
text: {
|
|
26
|
+
type: String,
|
|
27
|
+
required: false
|
|
28
|
+
},
|
|
29
|
+
maxLines: {
|
|
30
|
+
type: Number,
|
|
31
|
+
required: false
|
|
32
|
+
},
|
|
33
|
+
baseFontSize: {
|
|
34
|
+
type: Number,
|
|
35
|
+
required: false
|
|
36
|
+
},
|
|
37
|
+
minFontSize: {
|
|
38
|
+
type: Number,
|
|
39
|
+
required: false
|
|
40
|
+
}
|
|
41
|
+
}, defaultTextShrinkProps),
|
|
42
|
+
setup(__props, { expose: __expose }) {
|
|
43
|
+
const props = __props;
|
|
44
|
+
const bem = createBem("text-shrink");
|
|
45
|
+
/** 缩放下限:浏览器可渲染的最小字号 */
|
|
46
|
+
const rootRef = useTemplateRef("root");
|
|
47
|
+
const measureRef = useTemplateRef("measure");
|
|
48
|
+
const textRef = useTemplateRef("text");
|
|
49
|
+
/** 当前生效字号,undefined 表示尚未完成首次适配 */
|
|
50
|
+
const fontSize = ref();
|
|
51
|
+
/** 是否停在缩放下限后仍然溢出(已按 maxLines 截断) */
|
|
52
|
+
const overflow = ref(false);
|
|
53
|
+
/** 首次适配完成前隐藏可见层,避免以错误字号闪一下 */
|
|
54
|
+
const ready = ref(false);
|
|
55
|
+
const lines = computed(() => {
|
|
56
|
+
const value = Math.floor(Number(props.maxLines));
|
|
57
|
+
return Number.isFinite(value) && value > 1 ? value : 1;
|
|
58
|
+
});
|
|
59
|
+
const singleLine = computed(() => lines.value === 1);
|
|
60
|
+
let frozenBaseFontSize;
|
|
61
|
+
function resolveBaseFontSize() {
|
|
62
|
+
const { baseFontSize } = props;
|
|
63
|
+
if (typeof baseFontSize === "number" && Number.isFinite(baseFontSize) && baseFontSize > 0) return baseFontSize;
|
|
64
|
+
if (frozenBaseFontSize !== void 0) return frozenBaseFontSize;
|
|
65
|
+
let value = FALLBACK_BASE_FONT_SIZE;
|
|
66
|
+
const root = rootRef.value;
|
|
67
|
+
if (root && typeof window !== "undefined") {
|
|
68
|
+
const measured = Number.parseFloat(window.getComputedStyle(root).fontSize);
|
|
69
|
+
if (Number.isFinite(measured) && measured > 0) value = measured;
|
|
70
|
+
}
|
|
71
|
+
frozenBaseFontSize = value;
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 缩放下限:取 minFontSize,并夹在 [MIN_FONT_SIZE, baseFontSize] 之间。
|
|
76
|
+
* 传 0 / 负数 / NaN 一律回退到浏览器可渲染下限,避免出现 0px 的死循环。
|
|
77
|
+
*/
|
|
78
|
+
function resolveMinFontSize(baseFontSize) {
|
|
79
|
+
const { minFontSize } = props;
|
|
80
|
+
return Math.min(baseFontSize, Math.max(MIN_FONT_SIZE, typeof minFontSize === "number" && Number.isFinite(minFontSize) && minFontSize > 0 ? minFontSize : MIN_FONT_SIZE));
|
|
81
|
+
}
|
|
82
|
+
let range;
|
|
83
|
+
function getRange() {
|
|
84
|
+
if (!range && typeof document !== "undefined") range = document.createRange();
|
|
85
|
+
return range;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 以指定字号测量一次:写入字号后只读一次几何。
|
|
89
|
+
* 单行读 Range 宽度(测量层此时不换行,宽度可以超出容器);
|
|
90
|
+
* 多行读 Range 的 rect 个数,一个文本节点每行对应一个 rect。
|
|
91
|
+
* 读写之间会强制一次布局,所以不需要等 Vue 更新。
|
|
92
|
+
*/
|
|
93
|
+
function probeAt(size, available) {
|
|
94
|
+
const measure = measureRef.value;
|
|
95
|
+
const node = measure?.firstChild;
|
|
96
|
+
const selection = getRange();
|
|
97
|
+
if (!measure || !node || node.nodeType !== 3 || !selection) return {
|
|
98
|
+
fits: true,
|
|
99
|
+
width: 0
|
|
100
|
+
};
|
|
101
|
+
measure.style.fontSize = `${size}px`;
|
|
102
|
+
selection.selectNodeContents(node);
|
|
103
|
+
if (singleLine.value) {
|
|
104
|
+
const { width } = selection.getBoundingClientRect();
|
|
105
|
+
return {
|
|
106
|
+
fits: width <= available + SIZE_TOLERANCE,
|
|
107
|
+
width
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
fits: selection.getClientRects().length <= lines.value,
|
|
112
|
+
width: 0
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 在 (floorFontSize, baseFontSize] 区间内搜索能完整显示的最大字号。
|
|
117
|
+
* 单行先用「宽度 ∝ 字号」外推两次把区间夹紧,多行直接二分;
|
|
118
|
+
* 外推只决定下一个试探点,放不放得下始终由真实测量决定,精度不受影响。
|
|
119
|
+
*/
|
|
120
|
+
function search(baseFontSize, available, floorFontSize) {
|
|
121
|
+
const atBase = probeAt(baseFontSize, available);
|
|
122
|
+
if (atBase.fits) return {
|
|
123
|
+
size: baseFontSize,
|
|
124
|
+
overflow: false
|
|
125
|
+
};
|
|
126
|
+
if (!probeAt(floorFontSize, available).fits) return {
|
|
127
|
+
size: floorFontSize,
|
|
128
|
+
overflow: true
|
|
129
|
+
};
|
|
130
|
+
let lower = floorFontSize;
|
|
131
|
+
let upper = baseFontSize;
|
|
132
|
+
let estimate = atBase.width > 0 ? baseFontSize * available / atBase.width : NaN;
|
|
133
|
+
let interpolations = 0;
|
|
134
|
+
while (upper - lower > SEARCH_EPSILON) {
|
|
135
|
+
const useEstimate = interpolations < INTERPOLATION_LIMIT && estimate > lower && estimate < upper;
|
|
136
|
+
if (useEstimate) interpolations += 1;
|
|
137
|
+
const middle = useEstimate ? estimate : (lower + upper) / 2;
|
|
138
|
+
const result = probeAt(middle, available);
|
|
139
|
+
if (!result.fits) upper = middle;
|
|
140
|
+
else if (result.width >= available - SIZE_TOLERANCE) {
|
|
141
|
+
lower = middle;
|
|
142
|
+
break;
|
|
143
|
+
} else lower = middle;
|
|
144
|
+
estimate = result.width > 0 ? middle * available / result.width : NaN;
|
|
145
|
+
}
|
|
146
|
+
const maxTenths = Math.floor(baseFontSize * 10);
|
|
147
|
+
let tenths = Math.floor(lower * 10);
|
|
148
|
+
while (tenths < maxTenths && probeAt((tenths + 1) / 10, available).fits) tenths += 1;
|
|
149
|
+
return {
|
|
150
|
+
size: Math.max(floorFontSize, tenths / 10),
|
|
151
|
+
overflow: false
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function fit() {
|
|
155
|
+
const root = rootRef.value;
|
|
156
|
+
const text = textRef.value;
|
|
157
|
+
if (!root || !text) return;
|
|
158
|
+
if (root.clientWidth <= 0) return;
|
|
159
|
+
const available = text.getBoundingClientRect().width;
|
|
160
|
+
if (available <= 0) return;
|
|
161
|
+
const baseFontSize = Math.max(MIN_FONT_SIZE, resolveBaseFontSize());
|
|
162
|
+
const floorFontSize = resolveMinFontSize(baseFontSize);
|
|
163
|
+
const result = search(baseFontSize, available, floorFontSize);
|
|
164
|
+
fontSize.value = Math.max(floorFontSize, result.size);
|
|
165
|
+
overflow.value = result.overflow;
|
|
166
|
+
ready.value = true;
|
|
167
|
+
observedWidth = available;
|
|
168
|
+
}
|
|
169
|
+
const requestFrame = (callback) => typeof requestAnimationFrame === "function" ? requestAnimationFrame(callback) : setTimeout(callback, 16);
|
|
170
|
+
const cancelFrame = (handle) => {
|
|
171
|
+
if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(handle);
|
|
172
|
+
else clearTimeout(handle);
|
|
173
|
+
};
|
|
174
|
+
let rafId = 0;
|
|
175
|
+
let disposed = false;
|
|
176
|
+
let resizeObserver;
|
|
177
|
+
/** 上一次适配时的宽度,用来跳过 ResizeObserver 的重复回调 */
|
|
178
|
+
let observedWidth = -1;
|
|
179
|
+
/** 同一帧内的多次触发合并成一次适配 */
|
|
180
|
+
function scheduleFit() {
|
|
181
|
+
if (disposed || rafId !== 0) return;
|
|
182
|
+
rafId = requestFrame(() => {
|
|
183
|
+
rafId = 0;
|
|
184
|
+
if (disposed) return;
|
|
185
|
+
fit();
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
watch(() => [
|
|
189
|
+
props.text,
|
|
190
|
+
props.maxLines,
|
|
191
|
+
props.baseFontSize,
|
|
192
|
+
props.minFontSize
|
|
193
|
+
], () => {
|
|
194
|
+
scheduleFit();
|
|
195
|
+
});
|
|
196
|
+
onMounted(() => {
|
|
197
|
+
fit();
|
|
198
|
+
if (typeof ResizeObserver !== "undefined" && rootRef.value) {
|
|
199
|
+
resizeObserver = new ResizeObserver((entries) => {
|
|
200
|
+
const width = entries[0]?.contentRect.width ?? 0;
|
|
201
|
+
if (Math.abs(width - observedWidth) < WIDTH_TOLERANCE) return;
|
|
202
|
+
observedWidth = width;
|
|
203
|
+
scheduleFit();
|
|
204
|
+
});
|
|
205
|
+
resizeObserver.observe(rootRef.value);
|
|
206
|
+
}
|
|
207
|
+
if (typeof document !== "undefined" && document.fonts && document.fonts.status !== "loaded") document.fonts.ready.then(() => {
|
|
208
|
+
if (!disposed) scheduleFit();
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
onBeforeUnmount(() => {
|
|
212
|
+
disposed = true;
|
|
213
|
+
resizeObserver?.disconnect();
|
|
214
|
+
resizeObserver = void 0;
|
|
215
|
+
if (rafId !== 0) {
|
|
216
|
+
cancelFrame(rafId);
|
|
217
|
+
rafId = 0;
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
const rootClass = computed(() => [bem.b(), bem.is("measuring", !ready.value)]);
|
|
221
|
+
const measureClass = bem.e("measure");
|
|
222
|
+
const textClass = computed(() => [bem.e("text"), singleLine.value ? bem.em("text", "nowrap") : bem.em("text", "clamp")]);
|
|
223
|
+
const textStyle = computed(() => {
|
|
224
|
+
const style = {};
|
|
225
|
+
if (fontSize.value !== void 0) style.fontSize = `${fontSize.value}px`;
|
|
226
|
+
if (!singleLine.value) style["-webkit-line-clamp"] = lines.value;
|
|
227
|
+
return style;
|
|
228
|
+
});
|
|
229
|
+
__expose({
|
|
230
|
+
fontSize,
|
|
231
|
+
overflow,
|
|
232
|
+
refit: scheduleFit
|
|
233
|
+
});
|
|
234
|
+
return (_ctx, _cache) => {
|
|
235
|
+
return openBlock(), createElementBlock("div", {
|
|
236
|
+
ref: "root",
|
|
237
|
+
class: normalizeClass(rootClass.value)
|
|
238
|
+
}, [createCommentVNode(" 文本层:可见文本 + 隐藏测量层。\n 测量层之所以放在文本层内部,是为了让所有可继承的排版属性(font-*、letter-spacing、\n word-spacing、white-space、word-break、text-transform…)由继承自动对齐:\n 兄弟节点之间不会互相继承,那种结构只能在 JS 里逐个搬运属性,搬漏一个就量不准。\n 测量层绝对定位、脱离文档流,不影响可见文本的布局与固有宽度。\n 用 v-text 而不是插值,保证它的内容永远是一个文本节点,Range 才能直接选它来数行。 "), createElementVNode("div", {
|
|
239
|
+
ref: "text",
|
|
240
|
+
class: normalizeClass(textClass.value),
|
|
241
|
+
style: normalizeStyle(textStyle.value)
|
|
242
|
+
}, [createElementVNode("div", {
|
|
243
|
+
ref: "measure",
|
|
244
|
+
class: normalizeClass(unref(measureClass)),
|
|
245
|
+
"aria-hidden": "true",
|
|
246
|
+
textContent: toDisplayString(props.text)
|
|
247
|
+
}, null, 10, _hoisted_1), createTextVNode(" " + toDisplayString(props.text), 1)], 6)], 2);
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
//#endregion
|
|
252
|
+
export { text_shrink_default as default };
|
package/global.d.ts
CHANGED
|
@@ -152,6 +152,7 @@ declare module 'vue' {
|
|
|
152
152
|
STabs: typeof import('./components/tabs/tabs').default
|
|
153
153
|
STag: typeof import('./components/tag/tag').default
|
|
154
154
|
SText: typeof import('./components/text/text').default
|
|
155
|
+
STextShrink: typeof import('./components/text-shrink/text-shrink').default
|
|
155
156
|
STimeline: typeof import('./components/timeline/timeline').default
|
|
156
157
|
STimelineItem: typeof import('./components/timeline/timeline-item').default
|
|
157
158
|
SToast: typeof import('./components/toast/toast').default
|