@txstate-mws/svelte-components 1.5.1 → 1.5.3
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/dist/actions/glue.d.ts +3 -2
- package/dist/actions/glue.js +7 -6
- package/dist/components/MultiSelect.svelte +12 -3
- package/dist/components/PopupMenu.svelte +19 -2
- package/dist/components/PopupMenu.svelte.d.ts +1 -0
- package/dist/util/util.d.ts +1 -0
- package/dist/util/util.js +15 -0
- package/package.json +1 -1
package/dist/actions/glue.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export interface GlueArgs<T extends GlueAlignStore = GlueAlignStore> {
|
|
|
4
4
|
target: HTMLElement;
|
|
5
5
|
align?: GlueAlignOpts;
|
|
6
6
|
cover?: boolean;
|
|
7
|
+
gap?: number;
|
|
7
8
|
adjustparentheight?: boolean;
|
|
8
9
|
store?: SettableSubject<T>;
|
|
9
10
|
}
|
|
@@ -11,7 +12,7 @@ export interface GlueAlignStore {
|
|
|
11
12
|
valign: 'bottom' | 'top';
|
|
12
13
|
halign: 'left' | 'right';
|
|
13
14
|
}
|
|
14
|
-
export declare function glue(el: HTMLElement, { target, align, cover, adjustparentheight, store }: GlueArgs): {
|
|
15
|
-
update({ target: utarget, align: ualign, cover: ucover, store: ustore }: GlueArgs): void;
|
|
15
|
+
export declare function glue(el: HTMLElement, { target, align, cover, gap, adjustparentheight, store }: GlueArgs): {
|
|
16
|
+
update({ target: utarget, align: ualign, cover: ucover, gap: ugap, store: ustore }: GlueArgs): void;
|
|
16
17
|
destroy(): void;
|
|
17
18
|
};
|
package/dist/actions/glue.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { watchForMutations } from '../util/index.js';
|
|
2
2
|
import { equal } from 'txstate-utils';
|
|
3
|
-
export function glue(el, { target, align = 'auto', cover = false, adjustparentheight = false, store }) {
|
|
3
|
+
export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjustparentheight = false, store }) {
|
|
4
4
|
let halign;
|
|
5
5
|
let valign;
|
|
6
6
|
const parent = el.offsetParent;
|
|
@@ -34,7 +34,7 @@ export function glue(el, { target, align = 'auto', cover = false, adjustparenthe
|
|
|
34
34
|
const targetHeight = rect.height;
|
|
35
35
|
if (autoalign === 'bottomleft') {
|
|
36
36
|
requestAnimationFrame(() => {
|
|
37
|
-
el.style.top = `${rect.top + (cover ? 0 : targetHeight)}px`;
|
|
37
|
+
el.style.top = `${rect.top + (cover ? 0 : targetHeight) + gap}px`;
|
|
38
38
|
el.style.left = `${rect.left}px`;
|
|
39
39
|
el.style.bottom = '';
|
|
40
40
|
el.style.right = '';
|
|
@@ -45,7 +45,7 @@ export function glue(el, { target, align = 'auto', cover = false, adjustparenthe
|
|
|
45
45
|
}
|
|
46
46
|
else if (autoalign === 'bottomright') {
|
|
47
47
|
requestAnimationFrame(() => {
|
|
48
|
-
el.style.top = `${rect.top + (cover ? 0 : targetHeight)}px`;
|
|
48
|
+
el.style.top = `${rect.top + (cover ? 0 : targetHeight) + gap}px`;
|
|
49
49
|
el.style.left = '';
|
|
50
50
|
el.style.bottom = '';
|
|
51
51
|
el.style.right = `${rect.right}px`;
|
|
@@ -58,7 +58,7 @@ export function glue(el, { target, align = 'auto', cover = false, adjustparenthe
|
|
|
58
58
|
requestAnimationFrame(() => {
|
|
59
59
|
el.style.top = '';
|
|
60
60
|
el.style.left = `${rect.left}px`;
|
|
61
|
-
el.style.bottom = `${rect.bottom + (cover ? 0 : targetHeight)}px`;
|
|
61
|
+
el.style.bottom = `${rect.bottom + (cover ? 0 : targetHeight) + gap}px`;
|
|
62
62
|
el.style.right = '';
|
|
63
63
|
adjustParentHeight();
|
|
64
64
|
});
|
|
@@ -69,7 +69,7 @@ export function glue(el, { target, align = 'auto', cover = false, adjustparenthe
|
|
|
69
69
|
requestAnimationFrame(() => {
|
|
70
70
|
el.style.top = '';
|
|
71
71
|
el.style.left = '';
|
|
72
|
-
el.style.bottom = `${rect.bottom + (cover ? 0 : targetHeight)}px`;
|
|
72
|
+
el.style.bottom = `${rect.bottom + (cover ? 0 : targetHeight) + gap}px`;
|
|
73
73
|
el.style.right = `${rect.right}px`;
|
|
74
74
|
adjustParentHeight();
|
|
75
75
|
});
|
|
@@ -81,9 +81,10 @@ export function glue(el, { target, align = 'auto', cover = false, adjustparenthe
|
|
|
81
81
|
const { destroy: watchDestroy } = watchForMutations(reposition);
|
|
82
82
|
document.addEventListener('scroll', reposition, { capture: true });
|
|
83
83
|
return {
|
|
84
|
-
update({ target: utarget, align: ualign = 'auto', cover: ucover = false, store: ustore }) {
|
|
84
|
+
update({ target: utarget, align: ualign = 'auto', cover: ucover = false, gap: ugap = 0, store: ustore }) {
|
|
85
85
|
align = ualign;
|
|
86
86
|
cover = ucover;
|
|
87
|
+
gap = ugap;
|
|
87
88
|
if (ustore && ustore !== store) {
|
|
88
89
|
ustore.update(v => ({ ...v, valign, halign }));
|
|
89
90
|
store = ustore;
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
controlled by the parent component via the `getOptions` function that will be used as a
|
|
7
7
|
debounced callback on the contents of the text input.
|
|
8
8
|
-->
|
|
9
|
-
<script>import { createEventDispatcher } from 'svelte';
|
|
9
|
+
<script>import { createEventDispatcher, onMount } from 'svelte';
|
|
10
10
|
import { randomid, Cache } from 'txstate-utils';
|
|
11
11
|
import ScreenReaderOnly from './ScreenReaderOnly.svelte';
|
|
12
12
|
import DefaultPopupMenu from './PopupMenu.svelte';
|
|
13
13
|
import { modifierKey, selectionIsLeft } from '../util';
|
|
14
|
+
import { SafeStore } from '@txstate-mws/svelte-store';
|
|
14
15
|
export let name;
|
|
15
16
|
/**
|
|
16
17
|
* Function to pass to the component that tells it how to use the text in the text input to determine what
|
|
@@ -51,7 +52,9 @@ const dispatch = createEventDispatcher();
|
|
|
51
52
|
const optionsCache = new Cache(async (ipt) => {
|
|
52
53
|
return await getOptions(ipt);
|
|
53
54
|
}, { freshseconds: 5 });
|
|
54
|
-
|
|
55
|
+
const selectedStore = new SafeStore(selected);
|
|
56
|
+
$: $selectedStore = selected;
|
|
57
|
+
$: selectedSet = new Set($selectedStore.map(s => s.value));
|
|
55
58
|
// Stop showing our menu if the maxSelections limit has been reached.
|
|
56
59
|
$: if (maxSelections > 1 && selected.length >= maxSelections && menushown)
|
|
57
60
|
menushown = false;
|
|
@@ -137,6 +140,12 @@ function reactToHilite(..._) {
|
|
|
137
140
|
inputelement.removeAttribute('aria-activedescendant');
|
|
138
141
|
}
|
|
139
142
|
$: reactToHilite(hilitedpill, id);
|
|
143
|
+
let gap = 0;
|
|
144
|
+
onMount(() => {
|
|
145
|
+
const ul = inputelement.closest('.multiselect-selected');
|
|
146
|
+
if (ul != null)
|
|
147
|
+
gap = ((ul.offsetHeight - ul.clientHeight) / 2) + parseInt(window.getComputedStyle(ul).paddingBottom);
|
|
148
|
+
});
|
|
140
149
|
</script>
|
|
141
150
|
|
|
142
151
|
<fieldset>
|
|
@@ -166,7 +175,7 @@ $: reactToHilite(hilitedpill, id);
|
|
|
166
175
|
<slot></slot>
|
|
167
176
|
</fieldset>
|
|
168
177
|
<svelte:component this={PopupMenu} bind:menushown bind:hilited={popuphilited} bind:value={popupvalue} align='bottomleft'
|
|
169
|
-
{usePortal} {loading} {emptyText}
|
|
178
|
+
{usePortal} {loading} {emptyText} {gap}
|
|
170
179
|
{menuContainerClass} {menuClass} {menuItemClass} {menuItemHilitedClass} {menuDividerClass}
|
|
171
180
|
items={options} buttonelement={inputelement}
|
|
172
181
|
on:change={addSelection}/>
|
|
@@ -7,7 +7,7 @@ import { isNotBlank, randomid } from 'txstate-utils';
|
|
|
7
7
|
import { Store } from '@txstate-mws/svelte-store';
|
|
8
8
|
import { glue, portal } from '../actions';
|
|
9
9
|
import ScreenReaderOnly from './ScreenReaderOnly.svelte';
|
|
10
|
-
import { modifierKey } from '../util';
|
|
10
|
+
import { getScrollParents, modifierKey } from '../util';
|
|
11
11
|
const dispatch = createEventDispatcher();
|
|
12
12
|
/** The DOM element that will act as the "button" for this menu. The menu will be placed next to the
|
|
13
13
|
button and be controlled by the button. Keyboard access will be enabled when the button has focus.
|
|
@@ -34,6 +34,8 @@ of when this is useful is when you are trying to round corners of your button an
|
|
|
34
34
|
menu is above or below your button so you know which corners to round and which to leave square. Note that
|
|
35
35
|
the values do not update when the menu is hidden, so you may need to rely on `menushown` as well. */
|
|
36
36
|
export let computedalign = new Store({ valign: 'bottom', halign: 'left' });
|
|
37
|
+
/** Create some spacing between the specified button element and the menu, in pixels */
|
|
38
|
+
export let gap = 0;
|
|
37
39
|
/** Set to `true` if you want the parent element's styling to increase its minHeight when necessary. Useful
|
|
38
40
|
for parent elements not styled for variable size children and which you want override the the size of. */
|
|
39
41
|
export let adjustparentheight = false;
|
|
@@ -201,6 +203,9 @@ async function onblur(e) {
|
|
|
201
203
|
}
|
|
202
204
|
}
|
|
203
205
|
function cleanup(element) {
|
|
206
|
+
for (const observer of observers)
|
|
207
|
+
observer.disconnect();
|
|
208
|
+
observers = [];
|
|
204
209
|
if (element) {
|
|
205
210
|
element.removeEventListener('click', onbuttonclick);
|
|
206
211
|
element.removeEventListener('keydown', onkeydown);
|
|
@@ -215,6 +220,7 @@ function cleanup(element) {
|
|
|
215
220
|
onDestroy(() => cleanup(buttonelement));
|
|
216
221
|
// if buttonelement changes we need to handle listeners and aria
|
|
217
222
|
let lastbuttonelement;
|
|
223
|
+
let observers = [];
|
|
218
224
|
function reactToButtonElement(buttonelement) {
|
|
219
225
|
cleanup(lastbuttonelement);
|
|
220
226
|
lastbuttonelement = buttonelement;
|
|
@@ -223,6 +229,17 @@ function reactToButtonElement(buttonelement) {
|
|
|
223
229
|
buttonelement.addEventListener('click', onbuttonclick);
|
|
224
230
|
buttonelement.addEventListener('keydown', onkeydown);
|
|
225
231
|
buttonelement.addEventListener('blur', onblur);
|
|
232
|
+
const parents = getScrollParents(buttonelement);
|
|
233
|
+
for (const parent of [...parents, undefined]) {
|
|
234
|
+
const observer = new IntersectionObserver((entries) => {
|
|
235
|
+
for (const entry of entries) {
|
|
236
|
+
if (entry.intersectionRatio < 1)
|
|
237
|
+
menushown = false;
|
|
238
|
+
}
|
|
239
|
+
}, { root: parent, threshold: 1 });
|
|
240
|
+
observer.observe(buttonelement);
|
|
241
|
+
observers.push(observer);
|
|
242
|
+
}
|
|
226
243
|
}
|
|
227
244
|
}
|
|
228
245
|
$: reactToButtonElement(buttonelement);
|
|
@@ -240,7 +257,7 @@ $: hasSelected = showSelected && items.some(itm => 'value' in itm && itm.value =
|
|
|
240
257
|
|
|
241
258
|
{#if menushown}
|
|
242
259
|
<div use:portal={usePortal === true ? undefined : (usePortal || null)}
|
|
243
|
-
use:glue={{ target: buttonelement, align, cover, adjustparentheight, store: computedalign }}
|
|
260
|
+
use:glue={{ target: buttonelement, align, cover, gap, adjustparentheight, store: computedalign }}
|
|
244
261
|
class={menuContainerClass}>
|
|
245
262
|
<ul bind:this={menuelement} id={menuid} role='listbox' style={width ? `width: ${width}` : ''}
|
|
246
263
|
class={menuClass} class:hasSelected class:defaultmenu={!menuClass && !menuContainerClass}
|
|
@@ -22,6 +22,7 @@ declare const __propDef: {
|
|
|
22
22
|
of when this is useful is when you are trying to round corners of your button and need to know whether the
|
|
23
23
|
menu is above or below your button so you know which corners to round and which to leave square. Note that
|
|
24
24
|
the values do not update when the menu is hidden, so you may need to rely on `menushown` as well. */ computedalign?: Store<GlueAlignStore> | undefined;
|
|
25
|
+
/** Create some spacing between the specified button element and the menu, in pixels */ gap?: number | undefined;
|
|
25
26
|
/** Set to `true` if you want the parent element's styling to increase its minHeight when necessary. Useful
|
|
26
27
|
for parent elements not styled for variable size children and which you want override the the size of. */ adjustparentheight?: boolean | undefined;
|
|
27
28
|
/** If the menu would be clipped by an `overflow: hidden`, you can set this prop and it will be placed in
|
package/dist/util/util.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ export declare function classes(...classNames: (string | undefined)[]): string;
|
|
|
2
2
|
export declare function debounced(fn: (..._: any[]) => any, timeout: number): (...args: any[]) => void;
|
|
3
3
|
export declare function modifierKey(e: any): any;
|
|
4
4
|
export declare function selectionIsLeft(ele: HTMLInputElement | HTMLTextAreaElement): boolean;
|
|
5
|
+
export declare function getScrollParents(element: HTMLElement): any;
|
package/dist/util/util.js
CHANGED
|
@@ -14,3 +14,18 @@ export function modifierKey(e) {
|
|
|
14
14
|
export function selectionIsLeft(ele) {
|
|
15
15
|
return ele.selectionStart === 0 && ele.selectionEnd === 0;
|
|
16
16
|
}
|
|
17
|
+
export function getScrollParents(element) {
|
|
18
|
+
let style = getComputedStyle(element);
|
|
19
|
+
const excludeStaticParent = style.position === 'absolute';
|
|
20
|
+
if (style.position === 'fixed')
|
|
21
|
+
return [document.body];
|
|
22
|
+
for (let parent = element; (parent = parent.parentElement);) {
|
|
23
|
+
style = getComputedStyle(parent);
|
|
24
|
+
if (excludeStaticParent && style.position === 'static') {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (/(auto|scroll|hidden)/.test(style.overflow + style.overflowY + style.overflowX))
|
|
28
|
+
return [parent, ...getScrollParents(parent)];
|
|
29
|
+
}
|
|
30
|
+
return [document.body];
|
|
31
|
+
}
|