@indielayer/ui 1.17.0 → 1.18.0
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/README.md +2 -2
- package/docs/assets/css/tailwind.css +6 -0
- package/docs/components/common/CodePreview.vue +14 -9
- package/docs/components/common/DocsFeatures.vue +41 -0
- package/docs/components/common/DocsHero.vue +216 -0
- package/docs/components/common/DocumentPage.vue +99 -112
- package/docs/components/common/ExampleBlocks.vue +157 -0
- package/docs/components/toolbar/Toolbar.vue +11 -2
- package/docs/components/toolbar/ToolbarColorToggle.vue +4 -4
- package/docs/components/toolbar/ToolbarSearch.vue +59 -62
- package/docs/composables/useDocMeta.ts +47 -0
- package/docs/icons.ts +28 -0
- package/docs/layouts/default.vue +1 -3
- package/docs/layouts/simple.vue +3 -1
- package/docs/main.ts +5 -0
- package/docs/pages/colors.vue +56 -47
- package/docs/pages/component/select/size.vue +1 -1
- package/docs/pages/component/select/usage.vue +14 -7
- package/docs/pages/error.vue +5 -3
- package/docs/pages/icons.vue +64 -54
- package/docs/pages/index.vue +93 -82
- package/docs/pages/typography.vue +38 -28
- package/docs/router/index.ts +31 -3
- package/docs/search/components.json +1 -1
- package/docs/search/index.json +1 -0
- package/lib/components/container/theme/Container.base.theme.js +1 -1
- package/lib/components/divider/theme/Divider.base.theme.js +1 -1
- package/lib/components/input/Input.vue.js +23 -24
- package/lib/components/select/Select.vue.d.ts +16 -27
- package/lib/components/select/Select.vue.js +451 -344
- package/lib/index.js +1 -1
- package/lib/index.umd.js +4 -4
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/virtual/components/virtualList/VirtualList.vue.js +33 -31
- package/lib/virtual/components/virtualList/useDynamicRowHeight.js +18 -19
- package/package.json +8 -3
- package/src/components/container/theme/Container.base.theme.ts +1 -1
- package/src/components/divider/theme/Divider.base.theme.ts +1 -1
- package/src/components/input/Input.vue +1 -2
- package/src/components/select/Select.vue +94 -18
- package/src/version.ts +1 -1
- package/src/virtual/components/virtualList/VirtualList.test.ts +143 -26
- package/src/virtual/components/virtualList/VirtualList.vue +12 -18
- package/src/virtual/components/virtualList/useDynamicRowHeight.test.ts +22 -8
- package/src/virtual/components/virtualList/useDynamicRowHeight.ts +4 -2
- package/src/virtual/utils/parseNumericStyleValue.ts +2 -0
package/lib/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare const _default: "1.
|
|
1
|
+
declare const _default: "1.18.0";
|
|
2
2
|
export default _default;
|
package/lib/version.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { defineComponent as
|
|
2
|
-
import { useVirtualizer as
|
|
3
|
-
import { isDynamicRowHeight as
|
|
4
|
-
import { DATA_ATTRIBUTE_LIST_INDEX as
|
|
5
|
-
const
|
|
1
|
+
import { defineComponent as k, ref as E, computed as i, watch as h, openBlock as w, createBlock as B, resolveDynamicComponent as D, normalizeClass as V, normalizeStyle as f, withCtx as P, createElementBlock as L, Fragment as N, renderList as O, renderSlot as g, createElementVNode as X, unref as Y } from "vue";
|
|
2
|
+
import { useVirtualizer as F } from "../../core/useVirtualizer.js";
|
|
3
|
+
import { isDynamicRowHeight as G } from "./isDynamicRowHeight.js";
|
|
4
|
+
import { DATA_ATTRIBUTE_LIST_INDEX as U } from "./useDynamicRowHeight.js";
|
|
5
|
+
const j = {
|
|
6
6
|
name: "XVirtualList"
|
|
7
|
-
},
|
|
8
|
-
...
|
|
7
|
+
}, Q = /* @__PURE__ */ k({
|
|
8
|
+
...j,
|
|
9
9
|
props: {
|
|
10
10
|
class: {},
|
|
11
11
|
defaultHeight: { default: 0 },
|
|
@@ -19,7 +19,7 @@ const G = {
|
|
|
19
19
|
tag: { default: "div" }
|
|
20
20
|
},
|
|
21
21
|
setup(v, { expose: y }) {
|
|
22
|
-
const t = v, n =
|
|
22
|
+
const t = v, n = E(null), p = i(() => t.rowProps || {}), x = i(() => t.rowCount), a = i(() => G(t.rowHeight)), H = i(() => {
|
|
23
23
|
if (a.value) {
|
|
24
24
|
const e = t.rowHeight, o = e.getAverageRowHeight();
|
|
25
25
|
return (r) => e.getRowHeight(r) ?? o;
|
|
@@ -28,19 +28,19 @@ const G = {
|
|
|
28
28
|
}), {
|
|
29
29
|
getCellBounds: R,
|
|
30
30
|
getEstimatedSize: C,
|
|
31
|
-
scrollToIndex:
|
|
31
|
+
scrollToIndex: I,
|
|
32
32
|
startIndexOverscan: u,
|
|
33
|
-
startIndexVisible:
|
|
33
|
+
startIndexVisible: z,
|
|
34
34
|
stopIndexOverscan: c,
|
|
35
|
-
stopIndexVisible:
|
|
36
|
-
} =
|
|
35
|
+
stopIndexVisible: b
|
|
36
|
+
} = F({
|
|
37
37
|
containerElement: n,
|
|
38
38
|
containerStyle: t.style,
|
|
39
39
|
defaultContainerSize: t.defaultHeight,
|
|
40
40
|
direction: "vertical",
|
|
41
|
-
itemCount:
|
|
41
|
+
itemCount: x,
|
|
42
42
|
itemProps: p,
|
|
43
|
-
itemSize:
|
|
43
|
+
itemSize: H,
|
|
44
44
|
onResize: t.onResize,
|
|
45
45
|
overscanCount: t.overscanCount
|
|
46
46
|
});
|
|
@@ -54,7 +54,7 @@ const G = {
|
|
|
54
54
|
index: r
|
|
55
55
|
}) {
|
|
56
56
|
var d, l;
|
|
57
|
-
const s =
|
|
57
|
+
const s = I({
|
|
58
58
|
align: e,
|
|
59
59
|
containerScrollOffset: ((d = n.value) == null ? void 0 : d.scrollTop) ?? 0,
|
|
60
60
|
index: r
|
|
@@ -66,19 +66,21 @@ const G = {
|
|
|
66
66
|
}
|
|
67
67
|
}), h(
|
|
68
68
|
[n, u, c, a, () => t.rowHeight],
|
|
69
|
-
([e, o, r, s]
|
|
70
|
-
|
|
69
|
+
([e, o, r, s], d, l) => {
|
|
70
|
+
if (!e || !s)
|
|
71
|
+
return;
|
|
72
|
+
const T = Array.from(e.children).filter((m, _) => {
|
|
71
73
|
if (m.hasAttribute("aria-hidden"))
|
|
72
74
|
return !1;
|
|
73
|
-
const
|
|
74
|
-
return m.setAttribute(
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
}
|
|
75
|
+
const $ = `${o + _}`;
|
|
76
|
+
return m.setAttribute(U, $), !0;
|
|
77
|
+
}), S = t.rowHeight;
|
|
78
|
+
l(S.observeRowElements(T));
|
|
79
|
+
},
|
|
78
80
|
{ flush: "post" }
|
|
79
81
|
// Run after DOM updates
|
|
80
82
|
), h(
|
|
81
|
-
[u,
|
|
83
|
+
[u, z, c, b],
|
|
82
84
|
([e, o, r, s]) => {
|
|
83
85
|
e >= 0 && r >= 0 && t.onRowsRendered && t.onRowsRendered(
|
|
84
86
|
{
|
|
@@ -117,10 +119,10 @@ const G = {
|
|
|
117
119
|
}
|
|
118
120
|
return e;
|
|
119
121
|
});
|
|
120
|
-
return (e, o) => (
|
|
122
|
+
return (e, o) => (w(), B(D(e.tag), {
|
|
121
123
|
ref_key: "element",
|
|
122
124
|
ref: n,
|
|
123
|
-
class:
|
|
125
|
+
class: V(e.$props.class),
|
|
124
126
|
style: f({
|
|
125
127
|
position: "relative",
|
|
126
128
|
maxHeight: "100%",
|
|
@@ -130,19 +132,19 @@ const G = {
|
|
|
130
132
|
}),
|
|
131
133
|
role: "list"
|
|
132
134
|
}, {
|
|
133
|
-
default:
|
|
134
|
-
(
|
|
135
|
+
default: P(() => [
|
|
136
|
+
(w(!0), L(N, null, O(A.value, (r) => g(e.$slots, "row", {
|
|
135
137
|
key: r.key,
|
|
136
138
|
index: r.index,
|
|
137
139
|
style: f(r.style),
|
|
138
140
|
ariaAttributes: r.ariaAttributes,
|
|
139
141
|
props: p.value
|
|
140
142
|
})), 128)),
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
g(e.$slots, "default"),
|
|
144
|
+
X("div", {
|
|
143
145
|
"aria-hidden": "",
|
|
144
146
|
style: f({
|
|
145
|
-
height: `${
|
|
147
|
+
height: `${Y(C)}px`,
|
|
146
148
|
width: "100%",
|
|
147
149
|
zIndex: -1
|
|
148
150
|
})
|
|
@@ -153,5 +155,5 @@ const G = {
|
|
|
153
155
|
}
|
|
154
156
|
});
|
|
155
157
|
export {
|
|
156
|
-
|
|
158
|
+
Q as default
|
|
157
159
|
};
|
|
@@ -1,56 +1,55 @@
|
|
|
1
|
-
import { ref as p, watch as d,
|
|
2
|
-
import { assert as
|
|
1
|
+
import { ref as p, watch as d, getCurrentInstance as H, onBeforeUnmount as R } from "vue";
|
|
2
|
+
import { assert as I } from "../../utils/assert.js";
|
|
3
3
|
const b = "data-virtual-index";
|
|
4
|
-
function
|
|
4
|
+
function y({
|
|
5
5
|
defaultRowHeight: v,
|
|
6
|
-
key:
|
|
6
|
+
key: o
|
|
7
7
|
}) {
|
|
8
|
-
const r = p(/* @__PURE__ */ new Map()),
|
|
9
|
-
|
|
10
|
-
r.value = /* @__PURE__ */ new Map(),
|
|
8
|
+
const r = p(/* @__PURE__ */ new Map()), a = p(0);
|
|
9
|
+
o !== void 0 && typeof o == "object" && "value" in o && d(o, () => {
|
|
10
|
+
r.value = /* @__PURE__ */ new Map(), a.value++;
|
|
11
11
|
});
|
|
12
12
|
const w = () => {
|
|
13
|
-
|
|
13
|
+
a.value;
|
|
14
14
|
let e = 0;
|
|
15
15
|
return r.value.forEach((t) => {
|
|
16
16
|
e += t;
|
|
17
17
|
}), e === 0 ? v : e / r.value.size;
|
|
18
18
|
}, m = (e) => {
|
|
19
|
-
|
|
19
|
+
a.value;
|
|
20
20
|
const t = r.value.get(e);
|
|
21
21
|
return t !== void 0 ? t : v;
|
|
22
22
|
}, A = (e, t) => {
|
|
23
23
|
if (r.value.get(e) === t)
|
|
24
24
|
return;
|
|
25
25
|
const n = new Map(r.value);
|
|
26
|
-
n.set(e, t), r.value = n,
|
|
26
|
+
n.set(e, t), r.value = n, a.value++;
|
|
27
27
|
}, E = (e) => {
|
|
28
28
|
if (e.length === 0)
|
|
29
29
|
return;
|
|
30
30
|
let t = !1;
|
|
31
31
|
const n = [];
|
|
32
32
|
if (e.forEach((s) => {
|
|
33
|
-
const { borderBoxSize: i, target: c } = s,
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
const { borderBoxSize: i, target: c } = s, g = c.getAttribute(b);
|
|
34
|
+
I(
|
|
35
|
+
g !== null,
|
|
36
36
|
`Invalid ${b} attribute value`
|
|
37
37
|
);
|
|
38
|
-
const
|
|
38
|
+
const h = parseInt(g), { blockSize: l } = i[0];
|
|
39
39
|
if (!l)
|
|
40
40
|
return;
|
|
41
|
-
r.value.get(
|
|
41
|
+
r.value.get(h) !== l && (n.push({ index: h, height: l }), t = !0);
|
|
42
42
|
}), t) {
|
|
43
43
|
const s = new Map(r.value);
|
|
44
44
|
n.forEach(({ index: i, height: c }) => {
|
|
45
45
|
s.set(i, c);
|
|
46
|
-
}), r.value = s,
|
|
46
|
+
}), r.value = s, a.value++;
|
|
47
47
|
}
|
|
48
48
|
}, u = new ResizeObserver(E);
|
|
49
|
-
H(f);
|
|
50
49
|
function f() {
|
|
51
50
|
u.disconnect();
|
|
52
51
|
}
|
|
53
|
-
return {
|
|
52
|
+
return H() && R(f), {
|
|
54
53
|
getAverageRowHeight: w,
|
|
55
54
|
getRowHeight: m,
|
|
56
55
|
setRowHeight: A,
|
|
@@ -65,5 +64,5 @@ function x({
|
|
|
65
64
|
}
|
|
66
65
|
export {
|
|
67
66
|
b as DATA_ATTRIBUTE_LIST_INDEX,
|
|
68
|
-
|
|
67
|
+
y as useDynamicRowHeight
|
|
69
68
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indielayer/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"description": "Indielayer UI Components with Tailwind CSS build for Vue 3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "João Teixeira",
|
|
@@ -50,12 +50,13 @@
|
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@indielayer/stylelint-config": "^1.0.0",
|
|
52
52
|
"@rushstack/eslint-patch": "^1.3.2",
|
|
53
|
+
"@unhead/vue": "^2.0.19",
|
|
53
54
|
"@tsconfig/node18": "^2.0.1",
|
|
54
55
|
"@types/jsdom": "^21.1.1",
|
|
55
56
|
"@types/node": "^18.16.18",
|
|
56
57
|
"@vitejs/plugin-vue": "^4.2.3",
|
|
57
58
|
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
|
58
|
-
"@vue/test-utils": "^2.4.
|
|
59
|
+
"@vue/test-utils": "^2.4.6",
|
|
59
60
|
"@vue/tsconfig": "^0.4.0",
|
|
60
61
|
"@vuepic/vue-datepicker": "^11.0.2",
|
|
61
62
|
"@vueuse/core": "^11.1.0",
|
|
@@ -107,9 +108,13 @@
|
|
|
107
108
|
"gen:types": "vue-tsc --declaration --emitDeclarationOnly -p tsconfig.vitest.json --composite false",
|
|
108
109
|
"gen:version": "node .scripts/gen-version.cjs",
|
|
109
110
|
"gen:search": "node .scripts/gen-search.cjs",
|
|
111
|
+
"gen:llms": "node .scripts/gen-llms.cjs",
|
|
112
|
+
"gen:sitemap": "node .scripts/gen-sitemap.cjs",
|
|
110
113
|
"test": "pnpm test:unit",
|
|
114
|
+
"test:ci": "vitest run --environment jsdom",
|
|
111
115
|
"test:unit": "vitest --environment jsdom",
|
|
112
116
|
"typecheck": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
|
|
113
|
-
"stylelint": "stylelint \"**/*.{css,vue,postcss,scss,sass}\" --ignore-path .gitignore"
|
|
117
|
+
"stylelint": "stylelint \"**/*.{css,vue,postcss,scss,sass}\" --ignore-path .gitignore",
|
|
118
|
+
"stylelint:fix": "stylelint \"**/*.{css,vue,postcss,scss,sass}\" --ignore-path .gitignore --fix"
|
|
114
119
|
}
|
|
115
120
|
}
|
|
@@ -4,7 +4,7 @@ const theme: DividerTheme = {
|
|
|
4
4
|
classes: {
|
|
5
5
|
wrapper: ({ props }) => `flex justify-center items-center ${props.vertical ? 'h-full flex-col' : 'w-full'}`,
|
|
6
6
|
|
|
7
|
-
label: 'font-medium text-
|
|
7
|
+
label: 'font-medium text-xs text-slate-400 dark:text-secondary-300',
|
|
8
8
|
|
|
9
9
|
line: 'bg-secondary-200 dark:bg-secondary-700 flex-grow',
|
|
10
10
|
},
|
|
@@ -152,14 +152,13 @@ defineExpose({ focus, blur, reset, validate, setError })
|
|
|
152
152
|
ref="elRef"
|
|
153
153
|
:class="[
|
|
154
154
|
classes.input,
|
|
155
|
-
type === 'password' ? 'pr-10' : '',
|
|
156
155
|
// error
|
|
157
156
|
errorInternal
|
|
158
157
|
? 'border-error-500 dark:border-error-400 focus:outline-error-500'
|
|
159
158
|
: 'focus:outline-[color:var(--x-input-border)]',
|
|
160
159
|
{
|
|
161
160
|
'!pl-10': iconLeft || icon,
|
|
162
|
-
'!pr-10': iconRight,
|
|
161
|
+
'!pr-10': iconRight || showPasswordToggle || showClearIcon,
|
|
163
162
|
},
|
|
164
163
|
]"
|
|
165
164
|
:disabled="disabled"
|
|
@@ -17,6 +17,9 @@ const selectProps = {
|
|
|
17
17
|
type: String,
|
|
18
18
|
default: 'Filter by...',
|
|
19
19
|
},
|
|
20
|
+
filterablePrefix: Boolean,
|
|
21
|
+
filterableSuffix: Boolean,
|
|
22
|
+
hideSelectedOptionSlots: Boolean,
|
|
20
23
|
virtualList: Boolean,
|
|
21
24
|
virtualListOffsetTop: Number,
|
|
22
25
|
virtualListOffsetBottom: Number,
|
|
@@ -110,12 +113,35 @@ const selected = computed<any | any[]>({
|
|
|
110
113
|
},
|
|
111
114
|
})
|
|
112
115
|
|
|
113
|
-
const
|
|
114
|
-
if (!props.options) return new Map<
|
|
116
|
+
const optionsByValue = computed(() => {
|
|
117
|
+
if (!props.options) return new Map<string | number, SelectOption>()
|
|
115
118
|
|
|
116
|
-
return new Map(props.options.map((option) => [option, option
|
|
119
|
+
return new Map(props.options.map((option) => [option.value, option]))
|
|
117
120
|
})
|
|
118
121
|
|
|
122
|
+
const filterCache = computed(() => {
|
|
123
|
+
if (!props.options) return new Map<SelectOption, { label: string; prefix?: string; suffix?: string; }>()
|
|
124
|
+
|
|
125
|
+
return new Map(props.options.map((option) => [
|
|
126
|
+
option,
|
|
127
|
+
{
|
|
128
|
+
label: option.label.toLowerCase(),
|
|
129
|
+
prefix: props.filterablePrefix && option.prefix ? option.prefix.toLowerCase() : undefined,
|
|
130
|
+
suffix: props.filterableSuffix && option.suffix ? option.suffix.toLowerCase() : undefined,
|
|
131
|
+
},
|
|
132
|
+
]))
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
function matchesFilter(option: SelectOption, filterLower: string) {
|
|
136
|
+
const cached = filterCache.value.get(option)
|
|
137
|
+
|
|
138
|
+
if (!cached) return false
|
|
139
|
+
|
|
140
|
+
return cached.label.includes(filterLower)
|
|
141
|
+
|| cached.prefix?.includes(filterLower)
|
|
142
|
+
|| cached.suffix?.includes(filterLower)
|
|
143
|
+
}
|
|
144
|
+
|
|
119
145
|
const internalOptions = computed(() => {
|
|
120
146
|
if (!props.options || props.options.length === 0) return []
|
|
121
147
|
|
|
@@ -128,10 +154,9 @@ const internalOptions = computed(() => {
|
|
|
128
154
|
: [],
|
|
129
155
|
)
|
|
130
156
|
const singleSelectedValue = !internalMultiple.value ? selected.value : null
|
|
131
|
-
const cache = labelCache.value
|
|
132
157
|
|
|
133
158
|
return props.options
|
|
134
|
-
.filter((option) => !hasFilter ||
|
|
159
|
+
.filter((option) => !hasFilter || matchesFilter(option, filterLower))
|
|
135
160
|
.map((option) => {
|
|
136
161
|
const isActive = internalMultiple.value
|
|
137
162
|
? selectedSet.has(option.value)
|
|
@@ -265,7 +290,7 @@ function findSelectableIndex(start: number | undefined, direction = 'down') {
|
|
|
265
290
|
}
|
|
266
291
|
|
|
267
292
|
function handleOptionClick(value: string | number) {
|
|
268
|
-
const option =
|
|
293
|
+
const option = getItem(value)
|
|
269
294
|
|
|
270
295
|
if (!option || option.disabled) return
|
|
271
296
|
|
|
@@ -326,8 +351,14 @@ function handleRemove(e: Event, value: string) {
|
|
|
326
351
|
}
|
|
327
352
|
}
|
|
328
353
|
|
|
354
|
+
function getItem(value: string | number | []) {
|
|
355
|
+
if (Array.isArray(value)) return undefined
|
|
356
|
+
|
|
357
|
+
return optionsByValue.value.get(value)
|
|
358
|
+
}
|
|
359
|
+
|
|
329
360
|
function getLabel(value: string | number | []) {
|
|
330
|
-
const option =
|
|
361
|
+
const option = getItem(value)
|
|
331
362
|
|
|
332
363
|
if (option) return option.label
|
|
333
364
|
|
|
@@ -542,15 +573,29 @@ defineExpose({ focus, blur, reset, validate, setError, filterRef })
|
|
|
542
573
|
:key="value"
|
|
543
574
|
size="xs"
|
|
544
575
|
removable
|
|
545
|
-
:outlined="!(isDisabled ||
|
|
546
|
-
:disabled="isDisabled ||
|
|
576
|
+
:outlined="!(isDisabled || getItem(value)?.disabled)"
|
|
577
|
+
:disabled="isDisabled || getItem(value)?.disabled"
|
|
547
578
|
:style="{ 'max-width': valueIndex === 0 && hiddenTagsCounterRef ? `calc(100% - ${hiddenTagsCounterRef.offsetWidth + 6 + 'px'})` : undefined }"
|
|
548
579
|
@remove="(e: Event) => { handleRemove(e, value) }"
|
|
549
580
|
>
|
|
550
|
-
<template
|
|
551
|
-
<
|
|
581
|
+
<template v-if="!hideSelectedOptionSlots">
|
|
582
|
+
<div class="flex items-center">
|
|
583
|
+
<span v-if="$slots.prefix || getItem(value)?.prefix" class="mr-2 shrink-0">
|
|
584
|
+
<slot name="prefix" :item="getItem(value)">{{ getItem(value)?.prefix }}</slot>
|
|
585
|
+
</span>
|
|
586
|
+
|
|
587
|
+
<span class="flex-1 truncate">
|
|
588
|
+
{{ getLabel(value) }}
|
|
589
|
+
</span>
|
|
590
|
+
|
|
591
|
+
<span v-if="$slots.suffix || getItem(value)?.suffix" class="ml-1 shrink-0">
|
|
592
|
+
<slot name="suffix" :item="getItem(value)">{{ getItem(value)?.suffix }}</slot>
|
|
593
|
+
</span>
|
|
594
|
+
</div>
|
|
595
|
+
</template>
|
|
596
|
+
<template v-else>
|
|
597
|
+
{{ getLabel(value) }}
|
|
552
598
|
</template>
|
|
553
|
-
{{ getLabel(value) }}
|
|
554
599
|
</x-tag>
|
|
555
600
|
|
|
556
601
|
<div
|
|
@@ -562,7 +607,24 @@ defineExpose({ focus, blur, reset, validate, setError, filterRef })
|
|
|
562
607
|
</div>
|
|
563
608
|
</template>
|
|
564
609
|
<template v-else-if="!internalMultiple && !isEmpty(selected) && getLabel(selected) !== ''">
|
|
565
|
-
|
|
610
|
+
<template v-if="!hideSelectedOptionSlots">
|
|
611
|
+
<div class="flex items-center">
|
|
612
|
+
<span v-if="$slots.prefix || getItem(selected)?.prefix" class="mr-2 shrink-0">
|
|
613
|
+
<slot name="prefix" :item="getItem(selected)">{{ getItem(selected)?.prefix }}</slot>
|
|
614
|
+
</span>
|
|
615
|
+
|
|
616
|
+
<span class="flex-1 truncate">
|
|
617
|
+
{{ getLabel(selected) }}
|
|
618
|
+
</span>
|
|
619
|
+
|
|
620
|
+
<span v-if="$slots.suffix || getItem(selected)?.suffix" class="ml-1 shrink-0">
|
|
621
|
+
<slot name="suffix" :item="getItem(selected)">{{ getItem(selected)?.suffix }}</slot>
|
|
622
|
+
</span>
|
|
623
|
+
</div>
|
|
624
|
+
</template>
|
|
625
|
+
<template v-else>
|
|
626
|
+
{{ getLabel(selected) }}
|
|
627
|
+
</template>
|
|
566
628
|
</template>
|
|
567
629
|
|
|
568
630
|
<template v-else>
|
|
@@ -636,14 +698,28 @@ defineExpose({ focus, blur, reset, validate, setError, filterRef })
|
|
|
636
698
|
:key="value"
|
|
637
699
|
size="xs"
|
|
638
700
|
removable
|
|
639
|
-
:outlined="!(isDisabled ||
|
|
640
|
-
:disabled="isDisabled ||
|
|
701
|
+
:outlined="!(isDisabled || getItem(value)?.disabled)"
|
|
702
|
+
:disabled="isDisabled || getItem(value)?.disabled"
|
|
641
703
|
@remove="(e: Event) => { handleRemove(e, value) }"
|
|
642
704
|
>
|
|
643
|
-
<template
|
|
644
|
-
<
|
|
705
|
+
<template v-if="!hideSelectedOptionSlots">
|
|
706
|
+
<div class="flex items-center">
|
|
707
|
+
<span v-if="$slots.prefix || getItem(value)?.prefix" class="mr-2 shrink-0">
|
|
708
|
+
<slot name="prefix" :item="getItem(value)">{{ getItem(value)?.prefix }}</slot>
|
|
709
|
+
</span>
|
|
710
|
+
|
|
711
|
+
<span class="flex-1 truncate">
|
|
712
|
+
{{ getLabel(value) }}
|
|
713
|
+
</span>
|
|
714
|
+
|
|
715
|
+
<span v-if="$slots.suffix || getItem(value)?.suffix" class="ml-1 shrink-0">
|
|
716
|
+
<slot name="suffix" :item="getItem(value)">{{ getItem(value)?.suffix }}</slot>
|
|
717
|
+
</span>
|
|
718
|
+
</div>
|
|
719
|
+
</template>
|
|
720
|
+
<template v-else>
|
|
721
|
+
{{ getLabel(value) }}
|
|
645
722
|
</template>
|
|
646
|
-
{{ getLabel(value) }}
|
|
647
723
|
</x-tag>
|
|
648
724
|
</x-popover-container>
|
|
649
725
|
</template>
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default '1.
|
|
1
|
+
export default '1.18.0'
|
|
@@ -1,47 +1,164 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { mount } from '@vue/test-utils'
|
|
3
|
-
import { h, type CSSProperties } from 'vue'
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
2
|
+
import { mount, flushPromises } from '@vue/test-utils'
|
|
3
|
+
import { h, nextTick, type CSSProperties } from 'vue'
|
|
4
4
|
import VirtualList from './VirtualList.vue'
|
|
5
|
+
import { useDynamicRowHeight, DATA_ATTRIBUTE_LIST_INDEX } from './useDynamicRowHeight'
|
|
6
|
+
import { mockResizeObserver, setElementSize } from '../../test-utils/mockResizeObserver'
|
|
7
|
+
import type { DynamicRowHeight } from './types'
|
|
5
8
|
|
|
6
9
|
interface RowSlotProps {
|
|
7
10
|
index: number;
|
|
8
11
|
style: CSSProperties;
|
|
9
12
|
}
|
|
10
13
|
|
|
14
|
+
const listStyle = { height: '400px' } as const
|
|
15
|
+
|
|
16
|
+
function mountVirtualList(
|
|
17
|
+
props: Record<string, unknown>,
|
|
18
|
+
dynamicRowHeight?: DynamicRowHeight,
|
|
19
|
+
) {
|
|
20
|
+
return mount(VirtualList, {
|
|
21
|
+
props: {
|
|
22
|
+
rowCount: 100,
|
|
23
|
+
rowHeight: dynamicRowHeight ?? 50,
|
|
24
|
+
style: listStyle,
|
|
25
|
+
...props,
|
|
26
|
+
},
|
|
27
|
+
slots: {
|
|
28
|
+
row: ({ index, style }: RowSlotProps) =>
|
|
29
|
+
h('div', { style, class: 'test-row' }, `Row ${index}`),
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function createMockDynamicRowHeight(defaultRowHeight = 50) {
|
|
35
|
+
const unobserve = vi.fn<[], void>()
|
|
36
|
+
|
|
37
|
+
const dynamicRowHeight: DynamicRowHeight = {
|
|
38
|
+
getAverageRowHeight: vi.fn(() => defaultRowHeight),
|
|
39
|
+
getRowHeight: vi.fn(() => undefined),
|
|
40
|
+
setRowHeight: vi.fn(),
|
|
41
|
+
observeRowElements: vi.fn(() => unobserve),
|
|
42
|
+
cleanup: vi.fn(),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { dynamicRowHeight, unobserve }
|
|
46
|
+
}
|
|
47
|
+
|
|
11
48
|
describe('VirtualList', () => {
|
|
12
49
|
it('renders correctly', () => {
|
|
13
|
-
const wrapper =
|
|
14
|
-
props: {
|
|
15
|
-
rowCount: 100,
|
|
16
|
-
rowHeight: 50,
|
|
17
|
-
style: { height: '400px' },
|
|
18
|
-
},
|
|
19
|
-
slots: {
|
|
20
|
-
row: ({ index, style }: RowSlotProps) =>
|
|
21
|
-
h('div', { style }, `Row ${index}`),
|
|
22
|
-
},
|
|
23
|
-
})
|
|
50
|
+
const wrapper = mountVirtualList({})
|
|
24
51
|
|
|
25
52
|
expect(wrapper.exists()).toBe(true)
|
|
26
53
|
expect(wrapper.attributes('role')).toBe('list')
|
|
27
54
|
})
|
|
28
55
|
|
|
29
56
|
it('renders visible rows', () => {
|
|
30
|
-
const wrapper =
|
|
31
|
-
props: {
|
|
32
|
-
rowCount: 100,
|
|
33
|
-
rowHeight: 50,
|
|
34
|
-
style: { height: '400px' },
|
|
35
|
-
},
|
|
36
|
-
slots: {
|
|
37
|
-
row: ({ index, style }: RowSlotProps) =>
|
|
38
|
-
h('div', { style, class: 'test-row' }, `Row ${index}`),
|
|
39
|
-
},
|
|
40
|
-
})
|
|
57
|
+
const wrapper = mountVirtualList({})
|
|
41
58
|
|
|
42
|
-
// Should render some rows (visible + overscan)
|
|
43
59
|
const rows = wrapper.findAll('.test-row')
|
|
44
60
|
|
|
45
61
|
expect(rows.length).toBeGreaterThan(0)
|
|
46
62
|
})
|
|
63
|
+
|
|
64
|
+
describe('dynamic row height watch', () => {
|
|
65
|
+
it('does not observe row elements when rowHeight is fixed', async () => {
|
|
66
|
+
const { dynamicRowHeight } = createMockDynamicRowHeight()
|
|
67
|
+
|
|
68
|
+
mountVirtualList({ rowHeight: 50 })
|
|
69
|
+
|
|
70
|
+
await flushPromises()
|
|
71
|
+
await nextTick()
|
|
72
|
+
|
|
73
|
+
expect(dynamicRowHeight.observeRowElements).not.toHaveBeenCalled()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('observes visible row elements and sets data-virtual-index', async () => {
|
|
77
|
+
const { dynamicRowHeight } = createMockDynamicRowHeight()
|
|
78
|
+
|
|
79
|
+
const wrapper = mountVirtualList({ rowHeight: dynamicRowHeight })
|
|
80
|
+
|
|
81
|
+
await flushPromises()
|
|
82
|
+
await nextTick()
|
|
83
|
+
|
|
84
|
+
expect(dynamicRowHeight.observeRowElements).toHaveBeenCalled()
|
|
85
|
+
|
|
86
|
+
const observedElements = vi.mocked(dynamicRowHeight.observeRowElements).mock
|
|
87
|
+
.calls[0]?.[0] as Element[]
|
|
88
|
+
|
|
89
|
+
expect(observedElements.length).toBeGreaterThan(0)
|
|
90
|
+
expect(
|
|
91
|
+
observedElements.every((element) => !element.hasAttribute('aria-hidden')),
|
|
92
|
+
).toBe(true)
|
|
93
|
+
|
|
94
|
+
const listElement = wrapper.element as HTMLElement
|
|
95
|
+
const rowElements = Array.from(listElement.children).filter(
|
|
96
|
+
(child) => !child.hasAttribute('aria-hidden'),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
expect(rowElements.length).toBe(observedElements.length)
|
|
100
|
+
rowElements.forEach((element) => {
|
|
101
|
+
expect(element.hasAttribute(DATA_ATTRIBUTE_LIST_INDEX)).toBe(true)
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('runs previous unobserve when the observed range changes', async () => {
|
|
106
|
+
const { dynamicRowHeight, unobserve } = createMockDynamicRowHeight()
|
|
107
|
+
|
|
108
|
+
const wrapper = mountVirtualList({ rowHeight: dynamicRowHeight })
|
|
109
|
+
|
|
110
|
+
await flushPromises()
|
|
111
|
+
await nextTick()
|
|
112
|
+
|
|
113
|
+
expect(dynamicRowHeight.observeRowElements).toHaveBeenCalledTimes(1)
|
|
114
|
+
expect(unobserve).not.toHaveBeenCalled()
|
|
115
|
+
|
|
116
|
+
const listElement = wrapper.element as HTMLDivElement
|
|
117
|
+
|
|
118
|
+
listElement.scrollTop = 2500
|
|
119
|
+
listElement.dispatchEvent(new Event('scroll'))
|
|
120
|
+
await flushPromises()
|
|
121
|
+
await nextTick()
|
|
122
|
+
|
|
123
|
+
expect(unobserve).toHaveBeenCalledTimes(1)
|
|
124
|
+
expect(dynamicRowHeight.observeRowElements).toHaveBeenCalledTimes(2)
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe('dynamic row height integration', () => {
|
|
129
|
+
let unmockResizeObserver: (() => void) | undefined
|
|
130
|
+
let dynamicRowHeight: DynamicRowHeight | undefined
|
|
131
|
+
|
|
132
|
+
beforeEach(() => {
|
|
133
|
+
unmockResizeObserver = mockResizeObserver()
|
|
134
|
+
dynamicRowHeight = useDynamicRowHeight({ defaultRowHeight: 50 })
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
afterEach(() => {
|
|
138
|
+
dynamicRowHeight?.cleanup()
|
|
139
|
+
unmockResizeObserver?.()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('updates measured heights when observed rows resize', async () => {
|
|
143
|
+
const wrapper = mountVirtualList({ rowHeight: dynamicRowHeight })
|
|
144
|
+
|
|
145
|
+
await flushPromises()
|
|
146
|
+
await nextTick()
|
|
147
|
+
|
|
148
|
+
const rowElement = wrapper.find('.test-row').element as HTMLElement
|
|
149
|
+
|
|
150
|
+
setElementSize({
|
|
151
|
+
element: rowElement,
|
|
152
|
+
width: 100,
|
|
153
|
+
height: 72,
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
await flushPromises()
|
|
157
|
+
await nextTick()
|
|
158
|
+
|
|
159
|
+
const index = Number(rowElement.getAttribute(DATA_ATTRIBUTE_LIST_INDEX))
|
|
160
|
+
|
|
161
|
+
expect(dynamicRowHeight?.getRowHeight(index)).toBe(72)
|
|
162
|
+
})
|
|
163
|
+
})
|
|
47
164
|
})
|