@7365admin1/layer-common 4.73.1-staging.463 → 4.73.1-staging.465
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.
|
@@ -818,6 +818,20 @@ select option {
|
|
|
818
818
|
text-align: center;
|
|
819
819
|
}
|
|
820
820
|
|
|
821
|
+
/* The footer of a paging AppSelect. Same voice as the empty state - it is the
|
|
822
|
+
same kind of message about the list rather than an option in it - with a rule
|
|
823
|
+
above so it does not read as one more row to click. */
|
|
824
|
+
.app-select__loading {
|
|
825
|
+
margin: 6px 0 0;
|
|
826
|
+
padding: 9px 8px;
|
|
827
|
+
border-top: 1px solid var(--border);
|
|
828
|
+
font-family: var(--font-sans);
|
|
829
|
+
font-size: var(--fs-cell);
|
|
830
|
+
font-weight: 600;
|
|
831
|
+
color: var(--muted);
|
|
832
|
+
text-align: center;
|
|
833
|
+
}
|
|
834
|
+
|
|
821
835
|
.app-select__option {
|
|
822
836
|
display: flex;
|
|
823
837
|
align-items: center;
|
package/components/AppSelect.vue
CHANGED
|
@@ -53,7 +53,12 @@
|
|
|
53
53
|
</div>
|
|
54
54
|
</template>
|
|
55
55
|
|
|
56
|
-
<div
|
|
56
|
+
<div
|
|
57
|
+
ref="singleMenu"
|
|
58
|
+
class="app-select__menu"
|
|
59
|
+
role="listbox"
|
|
60
|
+
@scroll.passive="onMenuScroll"
|
|
61
|
+
>
|
|
57
62
|
<div v-if="searchable" class="app-select__search">
|
|
58
63
|
<v-icon icon="mdi-magnify" size="16" />
|
|
59
64
|
<input
|
|
@@ -83,6 +88,11 @@
|
|
|
83
88
|
<p v-if="searchable && !visibleOptions.length" class="app-select__empty">
|
|
84
89
|
{{ normalized.length ? "No matches" : "Nothing to choose from" }}
|
|
85
90
|
</p>
|
|
91
|
+
<!-- In the list, not under the field: the wait belongs where the reader's
|
|
92
|
+
eye already is, at the end of what they just scrolled through. -->
|
|
93
|
+
<p v-if="loadingMore" class="app-select__loading" role="status">
|
|
94
|
+
Loading more...
|
|
95
|
+
</p>
|
|
86
96
|
</div>
|
|
87
97
|
</v-menu>
|
|
88
98
|
|
|
@@ -184,6 +194,34 @@ defineOptions({ inheritAttrs: false });
|
|
|
184
194
|
*/
|
|
185
195
|
const model = defineModel<any>({ default: "" });
|
|
186
196
|
|
|
197
|
+
/**
|
|
198
|
+
* ASKING FOR THE NEXT PAGE, FROM INSIDE THE LIST.
|
|
199
|
+
*
|
|
200
|
+
* A dropdown that pages should load as it is scrolled - a button under the
|
|
201
|
+
* field asks the reader to leave the list they are reading to fetch more of it.
|
|
202
|
+
* The menu is already the scroll container (`max-height: 280px; overflow-y:
|
|
203
|
+
* auto` in `primitives.css`), so the end of the list is the natural trigger.
|
|
204
|
+
*/
|
|
205
|
+
const emit = defineEmits<{ (e: "load-more"): void }>();
|
|
206
|
+
|
|
207
|
+
/** How close to the end counts as the end. About one row and a half. */
|
|
208
|
+
const LOAD_MORE_SLACK_PX = 48;
|
|
209
|
+
|
|
210
|
+
const singleMenu = ref<HTMLElement | null>(null);
|
|
211
|
+
/* Scroll fires per pixel. This makes the ask happen once per ARRIVAL at the
|
|
212
|
+
end, not once per event, and rearms when the list grows under the reader. */
|
|
213
|
+
const askedAtBottom = ref(false);
|
|
214
|
+
|
|
215
|
+
function onMenuScroll(event: Event) {
|
|
216
|
+
const el = event.target as HTMLElement | null;
|
|
217
|
+
if (!el || !props.hasMore || props.loadingMore) return;
|
|
218
|
+
const near = el.scrollHeight - el.scrollTop - el.clientHeight <= LOAD_MORE_SLACK_PX;
|
|
219
|
+
if (near && !askedAtBottom.value) emit("load-more");
|
|
220
|
+
askedAtBottom.value = near;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
187
225
|
const props = defineProps({
|
|
188
226
|
label: { type: String, default: "" },
|
|
189
227
|
/** Strings, or the `{ title, value }` shape `v-select` already takes. */
|
|
@@ -204,6 +242,15 @@ const props = defineProps({
|
|
|
204
242
|
* enough to read does not want a search field in front of it.
|
|
205
243
|
*/
|
|
206
244
|
searchable: { type: Boolean, default: false },
|
|
245
|
+
/**
|
|
246
|
+
* MORE OPTIONS EXIST THAN ARE LOADED - see the `load-more` emit.
|
|
247
|
+
*
|
|
248
|
+
* Opt-in, and single-select only for now. A caller that leaves these alone
|
|
249
|
+
* behaves exactly as it did: no footer, no listener, no emit.
|
|
250
|
+
*/
|
|
251
|
+
hasMore: { type: Boolean, default: false },
|
|
252
|
+
/** A page is on its way; shows the footer and stops further asking. */
|
|
253
|
+
loadingMore: { type: Boolean, default: false },
|
|
207
254
|
searchPlaceholder: { type: String, default: "Search" },
|
|
208
255
|
});
|
|
209
256
|
|
|
@@ -231,6 +278,24 @@ const visibleOptions = computed(() => {
|
|
|
231
278
|
);
|
|
232
279
|
});
|
|
233
280
|
|
|
281
|
+
/*
|
|
282
|
+
* A page that does not fill the menu leaves nothing to scroll, and the reader
|
|
283
|
+
* would be stranded with no way to reach the rest. That is not hypothetical
|
|
284
|
+
* here: a page of ten whose rows are mostly filtered out by the caller can draw
|
|
285
|
+
* four options in a box built for eight. So whenever the list settles short of
|
|
286
|
+
* its own container, the next page is asked for straight away.
|
|
287
|
+
*/
|
|
288
|
+
watch(
|
|
289
|
+
() => [visibleOptions.value.length, props.hasMore, props.loadingMore, singleOpen.value],
|
|
290
|
+
async () => {
|
|
291
|
+
if (!props.hasMore || props.loadingMore || !singleOpen.value) return;
|
|
292
|
+
await nextTick();
|
|
293
|
+
const el = singleMenu.value;
|
|
294
|
+
if (el && el.scrollHeight <= el.clientHeight) emit("load-more");
|
|
295
|
+
},
|
|
296
|
+
{ flush: "post" },
|
|
297
|
+
);
|
|
298
|
+
|
|
234
299
|
/* A stale term would hide the list the next time the popover opens, so it is
|
|
235
300
|
dropped on every open - and the caret goes into the box, since opening a
|
|
236
301
|
searchable select is almost always a prelude to typing. */
|