@txstate-mws/svelte-components 1.6.10 → 1.6.12
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/elementqueries.js +12 -11
- package/dist/actions/glue.d.ts +61 -4
- package/dist/actions/glue.js +130 -16
- package/dist/actions/offset.js +2 -2
- package/dist/actions/passactions.d.ts +2 -2
- package/dist/actions/stickyfixed.js +3 -3
- package/dist/components/ButtonMenu.svelte +1 -2
- package/dist/components/ButtonMenu.svelte.d.ts +2 -3
- package/dist/components/CardLayout.svelte +9 -9
- package/dist/components/ConditionalWrapper.svelte +0 -1
- package/dist/components/ConditionalWrapper.svelte.d.ts +1 -1
- package/dist/components/FocusLock.svelte +3 -3
- package/dist/components/FocusLock.svelte.d.ts +1 -1
- package/dist/components/Modal.svelte +0 -1
- package/dist/components/MultiSelect.svelte +40 -8
- package/dist/components/MultiSelect.svelte.d.ts +8 -3
- package/dist/components/PopupMenu.svelte +104 -61
- package/dist/components/PopupMenu.svelte.d.ts +18 -18
- package/dist/types/PopupMenu.d.ts +6 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +0 -2
- package/dist/util/bodyoffset.js +1 -1
- package/dist/util/util.js +1 -2
- package/package.json +14 -10
|
@@ -7,15 +7,15 @@ class ElementQueries {
|
|
|
7
7
|
#subscribers = 0;
|
|
8
8
|
#stores = new Map();
|
|
9
9
|
#processwidths() {
|
|
10
|
-
for (
|
|
10
|
+
for (const layer of this.#watchlist) {
|
|
11
11
|
const widths = [];
|
|
12
12
|
const attrs = [];
|
|
13
|
-
for (
|
|
14
|
-
widths.push(
|
|
15
|
-
attrs.push(
|
|
13
|
+
for (const el of layer) {
|
|
14
|
+
widths.push(el.offsetWidth);
|
|
15
|
+
attrs.push(el.getAttribute('data-eq'));
|
|
16
16
|
}
|
|
17
|
-
for (let j = 0; j <
|
|
18
|
-
const el =
|
|
17
|
+
for (let j = 0; j < layer.length; j++) {
|
|
18
|
+
const el = layer[j];
|
|
19
19
|
const w = widths[j];
|
|
20
20
|
let attrstr = '';
|
|
21
21
|
let finalw = 1600;
|
|
@@ -62,10 +62,10 @@ class ElementQueries {
|
|
|
62
62
|
#gotoanchor() {
|
|
63
63
|
// since we are dramatically resizing, we need to scroll to the proper place for an
|
|
64
64
|
// anchor tag
|
|
65
|
-
const m = document.location.hash.match(/^#([a-z][
|
|
65
|
+
const m = document.location.hash.match(/^#([a-z][a-zA-Z0-9_:.\-]*)$/iv);
|
|
66
66
|
if (m) {
|
|
67
67
|
const id = m[1];
|
|
68
|
-
requestAnimationFrame(() => { (document.getElementById(id)
|
|
68
|
+
requestAnimationFrame(() => { (document.getElementById(id) ?? document.querySelector(`[name="${id}"]`))?.scrollIntoView(true); });
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
fullresync() {
|
|
@@ -100,7 +100,7 @@ class ElementQueries {
|
|
|
100
100
|
// instead of faithfully subscribing and unsubscribing, so we'll add a subscriber that can't be
|
|
101
101
|
// decremented and it will stay activated forever
|
|
102
102
|
activate() {
|
|
103
|
-
this.#subscribers
|
|
103
|
+
this.#subscribers += 1;
|
|
104
104
|
this.#activateRaw();
|
|
105
105
|
}
|
|
106
106
|
#deactivate() {
|
|
@@ -111,14 +111,15 @@ class ElementQueries {
|
|
|
111
111
|
this.#hasInit = false;
|
|
112
112
|
}
|
|
113
113
|
subscribe(el, store) {
|
|
114
|
-
this.#subscribers
|
|
114
|
+
this.#subscribers += 1;
|
|
115
115
|
if (store)
|
|
116
116
|
this.#stores.set(el, store);
|
|
117
117
|
else
|
|
118
118
|
this.#stores.delete(el);
|
|
119
119
|
this.#activateRaw();
|
|
120
120
|
return () => {
|
|
121
|
-
|
|
121
|
+
this.#subscribers -= 1;
|
|
122
|
+
if (this.#subscribers === 0)
|
|
122
123
|
this.#deactivate();
|
|
123
124
|
};
|
|
124
125
|
}
|
package/dist/actions/glue.d.ts
CHANGED
|
@@ -1,17 +1,74 @@
|
|
|
1
1
|
import type { SettableSubject } from '@txstate-mws/svelte-store';
|
|
2
|
-
export type GlueAlignOpts = 'auto' | 'bottomleft' | 'bottomright' | 'topleft' | 'topright';
|
|
2
|
+
export type GlueAlignOpts = 'auto' | 'bottomleft' | 'bottomright' | 'topleft' | 'topright' | 'top' | 'bottom' | 'left' | 'right' | 'autoleft' | 'autoright' | 'topauto' | 'bottomauto' | 'automiddle' | 'middleauto' | 'middle';
|
|
3
3
|
export interface GlueArgs<T extends GlueAlignStore = GlueAlignStore> {
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* The HTML element to which the element should be glued. The target remains where
|
|
6
|
+
* it is; the element you placed the glue action on will become position: fixed and
|
|
7
|
+
* move to align with the target.
|
|
8
|
+
*/
|
|
9
|
+
target?: HTMLElement;
|
|
10
|
+
/**
|
|
11
|
+
* How to align the element relative to the target.
|
|
12
|
+
*
|
|
13
|
+
* Corner alignments:
|
|
14
|
+
* - `topleft`: element's bottom-left corner meets target's top-left corner
|
|
15
|
+
* - `topright`: element's bottom-right corner meets target's top-right corner
|
|
16
|
+
* - `bottomleft`: element's top-left corner meets target's bottom-left corner
|
|
17
|
+
* - `bottomright`: element's top-right corner meets target's bottom-right corner
|
|
18
|
+
*
|
|
19
|
+
* Side alignments (centered on the midpoint of the side):
|
|
20
|
+
* - `top`: element centered above target
|
|
21
|
+
* - `bottom`: element centered below target
|
|
22
|
+
* - `left`: element centered to the left of target
|
|
23
|
+
* - `right`: element centered to the right of target
|
|
24
|
+
*
|
|
25
|
+
* Auto alignments (choose based on available viewport space):
|
|
26
|
+
* - `auto`: choose between all four corners
|
|
27
|
+
* - `autoleft`: choose between `topleft` and `bottomleft`
|
|
28
|
+
* - `autoright`: choose between `topright` and `bottomright`
|
|
29
|
+
* - `topauto`: choose between `topleft` and `topright`
|
|
30
|
+
* - `bottomauto`: choose between `bottomleft` and `bottomright`
|
|
31
|
+
* - `automiddle`: choose between `top` and `bottom`
|
|
32
|
+
* - `middleauto`: choose between `left` and `right`
|
|
33
|
+
* - `middle`: choose between `top`, `bottom`, `left`, and `right`, or with `cover` set to true, simply center over the target
|
|
34
|
+
*
|
|
35
|
+
* When `cover` is false (default), the element is placed adjacent to the target.
|
|
36
|
+
* When `cover` is true, the element overlaps the target from the aligned side.
|
|
37
|
+
*
|
|
38
|
+
* @default 'auto'
|
|
39
|
+
*/
|
|
5
40
|
align?: GlueAlignOpts;
|
|
41
|
+
/**
|
|
42
|
+
* Adjusts the alignment setting behavior such that now the element will cover the target.
|
|
43
|
+
*/
|
|
6
44
|
cover?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* A pixel margin between the element and the target. When used with `cover`, will reveal
|
|
47
|
+
* a gap's worth of pixels of the target.
|
|
48
|
+
*/
|
|
7
49
|
gap?: number;
|
|
50
|
+
/**
|
|
51
|
+
* When true, increases the parent element's `min-height` to account for
|
|
52
|
+
* the positioned element's size, preventing the parent from collapsing
|
|
53
|
+
* when its child becomes `position: fixed`. Ignored if the parent has
|
|
54
|
+
* `overflow-y: auto`. The original `min-height` is restored on destroy.
|
|
55
|
+
*
|
|
56
|
+
* @default false
|
|
57
|
+
*/
|
|
8
58
|
adjustparentheight?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* The store that contains information about the alignment of the element. With align
|
|
61
|
+
* set to `auto`, this will tell you quickly which alignment is currently active.
|
|
62
|
+
*/
|
|
9
63
|
store?: SettableSubject<T>;
|
|
10
64
|
}
|
|
11
65
|
export interface GlueAlignStore {
|
|
12
|
-
valign: 'bottom' | 'top';
|
|
13
|
-
halign: 'left' | 'right';
|
|
66
|
+
valign: 'bottom' | 'top' | 'center';
|
|
67
|
+
halign: 'left' | 'right' | 'center';
|
|
14
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Make this element `position: fixed` and move it around to align with the target element.
|
|
71
|
+
*/
|
|
15
72
|
export declare function glue(el: HTMLElement, { target, align, cover, gap, adjustparentheight, store }: GlueArgs): {
|
|
16
73
|
update({ target: utarget, align: ualign, cover: ucover, gap: ugap, store: ustore }: GlueArgs): void;
|
|
17
74
|
destroy(): void;
|
package/dist/actions/glue.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import { watchForMutations } from '../util/index.js';
|
|
2
2
|
import { equal } from 'txstate-utils';
|
|
3
|
+
/**
|
|
4
|
+
* Make this element `position: fixed` and move it around to align with the target element.
|
|
5
|
+
*/
|
|
3
6
|
export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjustparentheight = false, store }) {
|
|
4
7
|
let halign;
|
|
5
8
|
let valign;
|
|
6
|
-
const parent = el.offsetParent;
|
|
9
|
+
const parent = el.offsetParent instanceof HTMLElement ? el.offsetParent : undefined;
|
|
7
10
|
const formerMinHeight = parent?.style.minHeight;
|
|
8
11
|
if (target)
|
|
9
12
|
el.style.position = 'fixed';
|
|
10
|
-
function adjustParentHeight() {
|
|
13
|
+
function adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY) {
|
|
11
14
|
if (!adjustparentheight)
|
|
12
15
|
return;
|
|
13
|
-
if (
|
|
16
|
+
if (parentOverflowY === 'auto')
|
|
14
17
|
return;
|
|
15
|
-
const minHeight =
|
|
16
|
-
if (minHeight >
|
|
17
|
-
|
|
18
|
-
parent.style.minHeight = String(minHeight) + 'px';
|
|
19
|
-
});
|
|
18
|
+
const minHeight = elOffsetTop + elOffsetHeight;
|
|
19
|
+
if (minHeight > parentClientHeight) {
|
|
20
|
+
parent.style.minHeight = String(minHeight) + 'px';
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
let lastrect;
|
|
@@ -34,13 +35,55 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
|
|
|
34
35
|
const rect = { left: tmpRect.left - fixedRect.left, right: document.documentElement.clientWidth - tmpRect.right - fixedRect.right, top: tmpRect.top - fixedRect.top, bottom: document.documentElement.clientHeight - tmpRect.bottom - fixedRect.bottom, width: tmpRect.width, height: tmpRect.height };
|
|
35
36
|
if (equal(rect, lastrect))
|
|
36
37
|
return;
|
|
38
|
+
lastrect = rect;
|
|
37
39
|
let autoalign = align;
|
|
40
|
+
const leftright = rect.right > rect.left ? 'left' : 'right';
|
|
41
|
+
const topbottom = rect.bottom > rect.top ? 'bottom' : 'top';
|
|
38
42
|
if (align === 'auto') {
|
|
39
|
-
const leftright = rect.right > rect.left ? 'left' : 'right';
|
|
40
|
-
const topbottom = rect.bottom > rect.top ? 'bottom' : 'top';
|
|
41
43
|
autoalign = topbottom + leftright;
|
|
42
44
|
}
|
|
45
|
+
else if (align === 'autoleft') {
|
|
46
|
+
autoalign = topbottom + 'left';
|
|
47
|
+
}
|
|
48
|
+
else if (align === 'autoright') {
|
|
49
|
+
autoalign = topbottom + 'right';
|
|
50
|
+
}
|
|
51
|
+
else if (align === 'topauto') {
|
|
52
|
+
autoalign = 'top' + leftright;
|
|
53
|
+
}
|
|
54
|
+
else if (align === 'bottomauto') {
|
|
55
|
+
autoalign = 'bottom' + leftright;
|
|
56
|
+
}
|
|
57
|
+
else if (align === 'automiddle') {
|
|
58
|
+
autoalign = topbottom;
|
|
59
|
+
}
|
|
60
|
+
else if (align === 'middleauto') {
|
|
61
|
+
autoalign = leftright;
|
|
62
|
+
}
|
|
63
|
+
else if (align === 'middle') {
|
|
64
|
+
if (cover) {
|
|
65
|
+
autoalign = 'middle';
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const maxSpace = Math.max(rect.top, rect.bottom, rect.left, rect.right);
|
|
69
|
+
if (maxSpace === rect.bottom)
|
|
70
|
+
autoalign = 'bottom';
|
|
71
|
+
else if (maxSpace === rect.top)
|
|
72
|
+
autoalign = 'top';
|
|
73
|
+
else if (maxSpace === rect.right)
|
|
74
|
+
autoalign = 'right';
|
|
75
|
+
else
|
|
76
|
+
autoalign = 'left';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
43
79
|
const targetHeight = rect.height;
|
|
80
|
+
const elRect = el.getBoundingClientRect();
|
|
81
|
+
const elWidth = elRect.width;
|
|
82
|
+
const elHeight = elRect.height;
|
|
83
|
+
const elOffsetTop = el.offsetTop;
|
|
84
|
+
const elOffsetHeight = el.offsetHeight;
|
|
85
|
+
const parentClientHeight = parent?.clientHeight ?? 0;
|
|
86
|
+
const parentOverflowY = parent?.style.overflowY;
|
|
44
87
|
if (autoalign === 'bottomleft') {
|
|
45
88
|
cancelAnimationFrame(timer);
|
|
46
89
|
timer = requestAnimationFrame(() => {
|
|
@@ -48,7 +91,7 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
|
|
|
48
91
|
el.style.left = `${rect.left}px`;
|
|
49
92
|
el.style.removeProperty('bottom');
|
|
50
93
|
el.style.removeProperty('right');
|
|
51
|
-
adjustParentHeight();
|
|
94
|
+
adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
|
|
52
95
|
});
|
|
53
96
|
valign = 'bottom';
|
|
54
97
|
halign = 'left';
|
|
@@ -60,7 +103,7 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
|
|
|
60
103
|
el.style.removeProperty('left');
|
|
61
104
|
el.style.removeProperty('bottom');
|
|
62
105
|
el.style.right = `${rect.right}px`;
|
|
63
|
-
adjustParentHeight();
|
|
106
|
+
adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
|
|
64
107
|
});
|
|
65
108
|
valign = 'bottom';
|
|
66
109
|
halign = 'right';
|
|
@@ -72,7 +115,7 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
|
|
|
72
115
|
el.style.left = `${rect.left}px`;
|
|
73
116
|
el.style.bottom = `${rect.bottom + (cover ? 0 : targetHeight) + gap}px`;
|
|
74
117
|
el.style.removeProperty('right');
|
|
75
|
-
adjustParentHeight();
|
|
118
|
+
adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
|
|
76
119
|
});
|
|
77
120
|
valign = 'top';
|
|
78
121
|
halign = 'left';
|
|
@@ -84,14 +127,85 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
|
|
|
84
127
|
el.style.removeProperty('left');
|
|
85
128
|
el.style.bottom = `${rect.bottom + (cover ? 0 : targetHeight) + gap}px`;
|
|
86
129
|
el.style.right = `${rect.right}px`;
|
|
87
|
-
adjustParentHeight();
|
|
130
|
+
adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
|
|
88
131
|
});
|
|
89
132
|
valign = 'top';
|
|
90
133
|
halign = 'right';
|
|
91
134
|
}
|
|
135
|
+
else if (autoalign === 'top') {
|
|
136
|
+
cancelAnimationFrame(timer);
|
|
137
|
+
timer = requestAnimationFrame(() => {
|
|
138
|
+
el.style.removeProperty('top');
|
|
139
|
+
el.style.left = `${rect.left + rect.width / 2 - elWidth / 2}px`;
|
|
140
|
+
el.style.bottom = `${rect.bottom + (cover ? 0 : targetHeight) + gap}px`;
|
|
141
|
+
el.style.removeProperty('right');
|
|
142
|
+
adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
|
|
143
|
+
});
|
|
144
|
+
valign = 'top';
|
|
145
|
+
halign = 'center';
|
|
146
|
+
}
|
|
147
|
+
else if (autoalign === 'bottom') {
|
|
148
|
+
cancelAnimationFrame(timer);
|
|
149
|
+
timer = requestAnimationFrame(() => {
|
|
150
|
+
el.style.top = `${rect.top + (cover ? 0 : targetHeight) + gap}px`;
|
|
151
|
+
el.style.left = `${rect.left + rect.width / 2 - elWidth / 2}px`;
|
|
152
|
+
el.style.removeProperty('bottom');
|
|
153
|
+
el.style.removeProperty('right');
|
|
154
|
+
adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
|
|
155
|
+
});
|
|
156
|
+
valign = 'bottom';
|
|
157
|
+
halign = 'center';
|
|
158
|
+
}
|
|
159
|
+
else if (autoalign === 'left') {
|
|
160
|
+
cancelAnimationFrame(timer);
|
|
161
|
+
timer = requestAnimationFrame(() => {
|
|
162
|
+
el.style.top = `${rect.top + rect.height / 2 - elHeight / 2}px`;
|
|
163
|
+
el.style.removeProperty('bottom');
|
|
164
|
+
if (cover) {
|
|
165
|
+
el.style.left = `${rect.left + gap}px`;
|
|
166
|
+
el.style.removeProperty('right');
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
el.style.removeProperty('left');
|
|
170
|
+
el.style.right = `${rect.right + rect.width + gap}px`;
|
|
171
|
+
}
|
|
172
|
+
adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
|
|
173
|
+
});
|
|
174
|
+
valign = 'center';
|
|
175
|
+
halign = 'left';
|
|
176
|
+
}
|
|
177
|
+
else if (autoalign === 'right') {
|
|
178
|
+
cancelAnimationFrame(timer);
|
|
179
|
+
timer = requestAnimationFrame(() => {
|
|
180
|
+
el.style.top = `${rect.top + rect.height / 2 - elHeight / 2}px`;
|
|
181
|
+
el.style.removeProperty('bottom');
|
|
182
|
+
if (cover) {
|
|
183
|
+
el.style.removeProperty('left');
|
|
184
|
+
el.style.right = `${rect.right + gap}px`;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
el.style.left = `${rect.left + rect.width + gap}px`;
|
|
188
|
+
el.style.removeProperty('right');
|
|
189
|
+
}
|
|
190
|
+
adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
|
|
191
|
+
});
|
|
192
|
+
valign = 'center';
|
|
193
|
+
halign = 'right';
|
|
194
|
+
}
|
|
195
|
+
else if (autoalign === 'middle') {
|
|
196
|
+
cancelAnimationFrame(timer);
|
|
197
|
+
timer = requestAnimationFrame(() => {
|
|
198
|
+
el.style.top = `${rect.top + rect.height / 2 - elHeight / 2}px`;
|
|
199
|
+
el.style.left = `${rect.left + rect.width / 2 - elWidth / 2}px`;
|
|
200
|
+
el.style.removeProperty('bottom');
|
|
201
|
+
el.style.removeProperty('right');
|
|
202
|
+
adjustParentHeight(elOffsetTop, elOffsetHeight, parentClientHeight, parentOverflowY);
|
|
203
|
+
});
|
|
204
|
+
valign = 'center';
|
|
205
|
+
halign = 'center';
|
|
206
|
+
}
|
|
92
207
|
store?.update(v => ({ ...v, valign, halign }));
|
|
93
208
|
}
|
|
94
|
-
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
95
209
|
const { destroy: watchDestroy } = watchForMutations(reposition);
|
|
96
210
|
document.addEventListener('scroll', reposition, { capture: true });
|
|
97
211
|
return {
|
|
@@ -116,7 +230,7 @@ export function glue(el, { target, align = 'auto', cover = false, gap = 0, adjus
|
|
|
116
230
|
destroy() {
|
|
117
231
|
watchDestroy();
|
|
118
232
|
document.removeEventListener('scroll', reposition);
|
|
119
|
-
if (adjustparentheight) {
|
|
233
|
+
if (adjustparentheight && parent) {
|
|
120
234
|
if (formerMinHeight)
|
|
121
235
|
parent.style.minHeight = formerMinHeight;
|
|
122
236
|
parent.style.removeProperty('min-height');
|
package/dist/actions/offset.js
CHANGED
|
@@ -16,11 +16,11 @@ export function offset(el, config) {
|
|
|
16
16
|
el.dispatchEvent(new CustomEvent('offset', { detail: current }));
|
|
17
17
|
}
|
|
18
18
|
let resolvedchange = config?.debounce ? debounced(lookforoffsetchange, config.debounce) : lookforoffsetchange;
|
|
19
|
-
const offsetContainer = !config?.container ? document.body : (config.container === 'nearest' ? el.offsetParent
|
|
19
|
+
const offsetContainer = !config?.container ? document.body : (config.container === 'nearest' ? el.offsetParent instanceof HTMLElement ? el.offsetParent : document.body : config.container);
|
|
20
20
|
const { destroy, update } = watchForPositionChangeInContainer(el, offsetContainer, resolvedchange);
|
|
21
21
|
return {
|
|
22
22
|
update(newConfig) {
|
|
23
|
-
const offsetContainer = !config?.container ? document.body : (config.container === 'nearest' ? el.offsetParent
|
|
23
|
+
const offsetContainer = !config?.container ? document.body : (config.container === 'nearest' ? el.offsetParent instanceof HTMLElement ? el.offsetParent : document.body : config.container);
|
|
24
24
|
if (newConfig?.debounce === true)
|
|
25
25
|
newConfig.debounce = 100;
|
|
26
26
|
if (newConfig?.store !== config?.store) {
|
|
@@ -6,5 +6,5 @@ export type SvelteHTMLActionType<P = any> = (node: HTMLElement, params?: P) => S
|
|
|
6
6
|
export type HTMLActionEntry<P = any> = SvelteHTMLActionType<P> | [SvelteHTMLActionType<P>, P];
|
|
7
7
|
export type SvelteSVGActionType<P = any> = (node: SVGElement, params?: P) => SvelteActionReturnType<P> | undefined;
|
|
8
8
|
export type SVGActionEntry<P = any> = SvelteSVGActionType<P> | [SvelteSVGActionType<P>, P];
|
|
9
|
-
export declare function passActions(node: HTMLElement, actions: HTMLActionEntry[]): SvelteActionReturnType<HTMLActionEntry[]>;
|
|
10
|
-
export declare function passActions(node: SVGElement, actions: SVGActionEntry[]): SvelteActionReturnType<SVGActionEntry[]>;
|
|
9
|
+
export declare function passActions(node: HTMLElement, actions: HTMLActionEntry[] | undefined): SvelteActionReturnType<HTMLActionEntry[]>;
|
|
10
|
+
export declare function passActions(node: SVGElement, actions: SVGActionEntry[] | undefined): SvelteActionReturnType<SVGActionEntry[]>;
|
|
@@ -63,10 +63,10 @@ export function stickyfixed(el, config) {
|
|
|
63
63
|
const cell = el.querySelector('th:last-child,td:last-child');
|
|
64
64
|
if (cell) {
|
|
65
65
|
const tstyle = window.getComputedStyle(table);
|
|
66
|
-
extrawidth = tstyle.borderCollapse === 'collapse' ? parseInt(window.getComputedStyle(cell).borderRightWidth) : 0;
|
|
66
|
+
extrawidth = tstyle.borderCollapse === 'collapse' ? parseInt(window.getComputedStyle(cell).borderRightWidth, 10) : 0;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
-
const width = `${parseInt(window.getComputedStyle(spacer).width) + extrawidth}px`;
|
|
69
|
+
const width = `${parseInt(window.getComputedStyle(spacer).width, 10) + extrawidth}px`;
|
|
70
70
|
elheight = spacer.offsetHeight;
|
|
71
71
|
for (let i = 0; i < tds.length; i++)
|
|
72
72
|
tds[i].style.width = widths[i];
|
|
@@ -126,7 +126,7 @@ export function stickyfixed(el, config) {
|
|
|
126
126
|
lastOffsetParent = offsetParent;
|
|
127
127
|
}
|
|
128
128
|
const table = el.closest('table');
|
|
129
|
-
const borderspacing = (table ? parseInt(window.getComputedStyle(table).borderSpacing) : 0);
|
|
129
|
+
const borderspacing = (table ? parseInt(window.getComputedStyle(table).borderSpacing, 10) : 0);
|
|
130
130
|
scrollstart = offset[0].top + el.offsetTop;
|
|
131
131
|
left = offset[0].left + (spacer.parentElement ? spacer.offsetLeft : el.offsetLeft);
|
|
132
132
|
if (el.tagName === 'TR') {
|
|
@@ -22,7 +22,6 @@ export let menuClass = '';
|
|
|
22
22
|
export let menuItemClass = '';
|
|
23
23
|
export let menuItemHilitedClass = '';
|
|
24
24
|
export let menuItemSelectedClass = '';
|
|
25
|
-
export let menuDividerClass = '';
|
|
26
25
|
const usemenurole = true;
|
|
27
26
|
</script>
|
|
28
27
|
|
|
@@ -49,4 +48,4 @@ const usemenurole = true;
|
|
|
49
48
|
{menuItemClass}
|
|
50
49
|
{menuItemHilitedClass}
|
|
51
50
|
{menuItemSelectedClass}
|
|
52
|
-
|
|
51
|
+
/>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { SvelteComponentTyped } from "svelte";
|
|
2
2
|
import { Store } from '@txstate-mws/svelte-store';
|
|
3
3
|
import type { GlueAlignOpts, GlueAlignStore } from '../actions';
|
|
4
|
-
import type {
|
|
4
|
+
import type { PopupMenuItem } from '../types';
|
|
5
5
|
declare const __propDef: {
|
|
6
6
|
props: {
|
|
7
7
|
buttonelement: HTMLButtonElement;
|
|
8
|
-
items?:
|
|
8
|
+
items?: PopupMenuItem[];
|
|
9
9
|
menushown?: boolean;
|
|
10
10
|
value?: string | undefined;
|
|
11
11
|
align?: GlueAlignOpts;
|
|
@@ -25,7 +25,6 @@ declare const __propDef: {
|
|
|
25
25
|
menuItemClass?: string;
|
|
26
26
|
menuItemHilitedClass?: string;
|
|
27
27
|
menuItemSelectedClass?: string;
|
|
28
|
-
menuDividerClass?: string;
|
|
29
28
|
};
|
|
30
29
|
events: {
|
|
31
30
|
[evt: string]: CustomEvent<any>;
|
|
@@ -22,7 +22,8 @@ setContext(CARDLAYOUT, {
|
|
|
22
22
|
blocks.splice(blocks.indexOf(block), 1);
|
|
23
23
|
hardrecalc();
|
|
24
24
|
});
|
|
25
|
-
block.order = writable(defaultOrder
|
|
25
|
+
block.order = writable(defaultOrder);
|
|
26
|
+
defaultOrder += 1;
|
|
26
27
|
block.linebreak = writable(false);
|
|
27
28
|
const cols = savecolumns || Math.ceil(1024 / maxwidth);
|
|
28
29
|
block.width = writable(`calc(${100.0 / cols}% - ${gutter * (cols - 1) / cols}px)`);
|
|
@@ -63,7 +64,7 @@ async function recalculate(realw) {
|
|
|
63
64
|
const cycling = detectcycle(realw);
|
|
64
65
|
if (columns !== savecolumns) {
|
|
65
66
|
for (const block of blocks)
|
|
66
|
-
block.width.set(`calc(${100.0 / columns}% - ${guttereach}px`);
|
|
67
|
+
block.width.set(`calc(${100.0 / columns}% - ${guttereach}px)`);
|
|
67
68
|
await tick();
|
|
68
69
|
}
|
|
69
70
|
// collect all the card heights at this new column width
|
|
@@ -93,11 +94,10 @@ async function recalculate(realw) {
|
|
|
93
94
|
tallestcol = colheight;
|
|
94
95
|
tallestcolidx = colidx;
|
|
95
96
|
}
|
|
96
|
-
colidx
|
|
97
|
+
colidx += 1;
|
|
97
98
|
colheight = 0;
|
|
98
99
|
}
|
|
99
|
-
|
|
100
|
-
arrangement[colidx] = [];
|
|
100
|
+
arrangement[colidx] ??= [];
|
|
101
101
|
arrangement[colidx].push(block);
|
|
102
102
|
colheight += block.height + gutter;
|
|
103
103
|
}
|
|
@@ -117,7 +117,7 @@ async function recalculate(realw) {
|
|
|
117
117
|
}
|
|
118
118
|
else {
|
|
119
119
|
optimal = [];
|
|
120
|
-
const heights = Array
|
|
120
|
+
const heights = [...Array(columns)].map((_) => 0); // initializes heights to an array of zeroes
|
|
121
121
|
// begin sorting blocks into columns
|
|
122
122
|
for (const block of blocks) {
|
|
123
123
|
// find the column with the smallest current height
|
|
@@ -125,8 +125,7 @@ async function recalculate(realw) {
|
|
|
125
125
|
// record the height we are adding to the chosen column
|
|
126
126
|
heights[colidx] += block.height + gutter;
|
|
127
127
|
// move the current card to the chosen column
|
|
128
|
-
|
|
129
|
-
optimal[colidx] = [];
|
|
128
|
+
optimal[colidx] ??= [];
|
|
130
129
|
optimal[colidx].push(block);
|
|
131
130
|
}
|
|
132
131
|
}
|
|
@@ -136,7 +135,8 @@ async function recalculate(realw) {
|
|
|
136
135
|
for (let i = 0; i < optimal.length; i++) {
|
|
137
136
|
for (let j = 0; j < optimal[i].length; j++) {
|
|
138
137
|
const block = optimal[i][j];
|
|
139
|
-
block.order.set(order
|
|
138
|
+
block.order.set(order);
|
|
139
|
+
order += 1;
|
|
140
140
|
block.linebreak.set(j === optimal[i].length - 1 && i < optimal.length - 1);
|
|
141
141
|
}
|
|
142
142
|
}
|
|
@@ -35,7 +35,6 @@ $: passthrough = (({ condition, component, a, span, element, ...passthrough }) =
|
|
|
35
35
|
<svelte:component this={component} bind:element {...passthrough}><slot></slot></svelte:component>
|
|
36
36
|
{:else}
|
|
37
37
|
{#if a}
|
|
38
|
-
<!-- svelte-ignore a11y-missing-attribute -->
|
|
39
38
|
<a bind:this={element} {...passthrough}><slot></slot></a>
|
|
40
39
|
{:else if span}
|
|
41
40
|
<span bind:this={element} {...passthrough}><slot></slot></span>
|
|
@@ -13,7 +13,7 @@ When they do, the escape event will be fired, allowing you to remove the dialog.
|
|
|
13
13
|
When it goes away, they'll be trapped inside the previous `FocusLock`, and so on.
|
|
14
14
|
-->
|
|
15
15
|
<script context="module" lang="ts">export const FocusLockStack = [];
|
|
16
|
-
const waitAtick = typeof requestAnimationFrame !== 'undefined' ? (resolve) => requestAnimationFrame(resolve) : resolve => resolve(0);
|
|
16
|
+
const waitAtick = typeof requestAnimationFrame !== 'undefined' ? (resolve) => requestAnimationFrame(resolve) : (resolve) => resolve(0);
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
19
|
<script lang="ts">import { onMount, onDestroy, createEventDispatcher } from 'svelte';
|
|
@@ -48,7 +48,7 @@ onMount(async () => {
|
|
|
48
48
|
pause: () => { state = 'paused'; },
|
|
49
49
|
unpause: () => { if (state === 'paused')
|
|
50
50
|
state = 'active'; },
|
|
51
|
-
update: () => dispatch('focuslockupdate')
|
|
51
|
+
update: () => { dispatch('focuslockupdate'); }
|
|
52
52
|
});
|
|
53
53
|
for (const entry of FocusLockStack)
|
|
54
54
|
entry.update();
|
|
@@ -123,7 +123,7 @@ const windowclick = (e) => {
|
|
|
123
123
|
};
|
|
124
124
|
</script>
|
|
125
125
|
<svelte:window on:mousedown={windowclick} />
|
|
126
|
-
<!-- svelte-ignore
|
|
126
|
+
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
|
127
127
|
<div class={className} role="alertdialog" aria-modal="true" on:click|stopPropagation on:mousedown|stopPropagation on:keydown|stopPropagation={keydown} on:focusin={focusin}>
|
|
128
128
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
|
129
129
|
<div bind:this={abovelockelement} tabindex="0"></div>
|
|
@@ -41,7 +41,6 @@ onMount(() => {
|
|
|
41
41
|
});
|
|
42
42
|
</script>
|
|
43
43
|
|
|
44
|
-
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
45
44
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
46
45
|
<div use:portal={usePortal} class="modal-backdrop" style:--modal-z={stackPosition * 10 + 3000} class:opaque on:mousedown|stopPropagation|preventDefault={() => { escapable && endmodal() }}>
|
|
47
46
|
<FocusLock bind:focusId class="modal-container {containerClass}" {includeselector} {escapable} on:escape {hidefocus} {hidefocuslabel} {returnfocusto} {initialfocus} on:focuslockupdate={onFocusLockUpdate}>
|
|
@@ -25,7 +25,7 @@ export let menuClass = '';
|
|
|
25
25
|
export let menuItemClass = '';
|
|
26
26
|
export let menuItemHilitedClass = '';
|
|
27
27
|
export let inputClass = '';
|
|
28
|
-
export let
|
|
28
|
+
export let menuCategoryClass = '';
|
|
29
29
|
/** The maximum number of selections allowed before making new selections is disabled. Default of 0 is unlimited. */
|
|
30
30
|
export let maxSelections = 0;
|
|
31
31
|
/** Consuming components may need to make decisions about what to display or return as options in the popup based
|
|
@@ -41,6 +41,13 @@ export let usePortal = undefined;
|
|
|
41
41
|
export let descid = undefined;
|
|
42
42
|
/** You can define your own PopupMenu and pass for that to be used or accept DefaultPopupMenu. */
|
|
43
43
|
export let PopupMenu = DefaultPopupMenu;
|
|
44
|
+
/** Add a Delete All button to clear all selected items */
|
|
45
|
+
export let includeDeleteAll = false;
|
|
46
|
+
/** Show a confirmation message before clearing all selections */
|
|
47
|
+
export let confirmDelete = undefined;
|
|
48
|
+
/** A function that can be used to customize the labels on the selected item pills. Default is a function
|
|
49
|
+
* that takes a PopupMenuItem and returns its label, if available, or its value when no label is provided. */
|
|
50
|
+
export let selectedItemLabel = item => item.label || item.value;
|
|
44
51
|
let menushown;
|
|
45
52
|
let loading = false;
|
|
46
53
|
let options = [];
|
|
@@ -51,9 +58,7 @@ let menuid;
|
|
|
51
58
|
let announcement = '';
|
|
52
59
|
const instructionid = randomid();
|
|
53
60
|
const dispatch = createEventDispatcher();
|
|
54
|
-
const optionsCache = new Cache(async (ipt) => {
|
|
55
|
-
return await getOptions(ipt);
|
|
56
|
-
}, { freshseconds: 5 });
|
|
61
|
+
const optionsCache = new Cache(async (ipt) => await getOptions(ipt), { freshseconds: 5 });
|
|
57
62
|
const selectedStore = new SafeStore(selected);
|
|
58
63
|
$: $selectedStore = selected;
|
|
59
64
|
$: selectedSet = new Set($selectedStore.map(s => s.value));
|
|
@@ -135,8 +140,24 @@ function inputfocus() {
|
|
|
135
140
|
}
|
|
136
141
|
function inputblur() {
|
|
137
142
|
active = false;
|
|
143
|
+
hilitedpill = undefined;
|
|
138
144
|
dispatch('blur');
|
|
139
145
|
}
|
|
146
|
+
function removeAllSelections() {
|
|
147
|
+
selected = [];
|
|
148
|
+
dispatch('change', selected);
|
|
149
|
+
}
|
|
150
|
+
function confirmRemoveAllSelections() {
|
|
151
|
+
if (confirmDelete) {
|
|
152
|
+
// eslint-disable-next-line no-alert -- just doing something super simple here
|
|
153
|
+
if (window.confirm(confirmDelete)) {
|
|
154
|
+
removeAllSelections();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
removeAllSelections();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
140
161
|
let popuphilited;
|
|
141
162
|
$: if (popuphilited != null)
|
|
142
163
|
hilitedpill = undefined;
|
|
@@ -156,11 +177,18 @@ let gap = 0;
|
|
|
156
177
|
onMount(() => {
|
|
157
178
|
const ul = inputelement.closest('.multiselect-selected');
|
|
158
179
|
if (ul != null)
|
|
159
|
-
gap = ((ul.offsetHeight - ul.clientHeight) / 2) + parseInt(window.getComputedStyle(ul).paddingBottom);
|
|
180
|
+
gap = ((ul.offsetHeight - ul.clientHeight) / 2) + parseInt(window.getComputedStyle(ul).paddingBottom, 10);
|
|
160
181
|
});
|
|
161
182
|
</script>
|
|
162
183
|
|
|
163
184
|
<div class="multiselect" class:disabled role="combobox" aria-controls={menushown ? menuid : undefined} aria-expanded={menushown}>
|
|
185
|
+
{#if includeDeleteAll && selected.length}
|
|
186
|
+
<div class="delete-all-container">
|
|
187
|
+
<button on:click|preventDefault|stopPropagation={confirmRemoveAllSelections}>
|
|
188
|
+
<slot name="deleteall">Delete All <ScreenReaderOnly>{selected.length} items</ScreenReaderOnly></slot>
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
191
|
+
{/if}
|
|
164
192
|
{#if selected.length}
|
|
165
193
|
<div class="multiselect-selected" role="listbox" aria-label="selected options">
|
|
166
194
|
{#each selected as option, i (option.value)}
|
|
@@ -168,14 +196,13 @@ onMount(() => {
|
|
|
168
196
|
<div id={id + option.value} role="option" tabindex="-1" class="multiselect-pill" class:hilited={hilitedpill === option.value}
|
|
169
197
|
on:click|preventDefault|stopPropagation={() => { !disabled && removeSelection(option, i, 1) }} on:mousedown={e => { disabled && e.preventDefault() }}
|
|
170
198
|
aria-selected="true" aria-posinset={i + 1} aria-setsize={selected.length}>
|
|
171
|
-
{option
|
|
199
|
+
{selectedItemLabel(option)}
|
|
172
200
|
<i aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256"><path fill="currentColor" d="M205.66 194.34a8 8 0 0 1-11.32 11.32L128 139.31l-66.34 66.35a8 8 0 0 1-11.32-11.32L116.69 128L50.34 61.66a8 8 0 0 1 11.32-11.32L128 116.69l66.34-66.35a8 8 0 0 1 11.32 11.32L139.31 128Z"/></svg></i>
|
|
173
201
|
<ScreenReaderOnly>, click to deselect</ScreenReaderOnly>
|
|
174
202
|
</div>
|
|
175
203
|
{/each}
|
|
176
204
|
</div>
|
|
177
205
|
{/if}
|
|
178
|
-
<!-- svelte-ignore a11y-role-has-required-aria-props -->
|
|
179
206
|
<input type="text" class={inputClass} {id} {name} {disabled} {placeholder}
|
|
180
207
|
bind:this={inputelement} on:input={reactToInput} on:blur={inputblur}
|
|
181
208
|
on:focus={inputfocus} on:keydown={inputkeydown}
|
|
@@ -188,7 +215,7 @@ onMount(() => {
|
|
|
188
215
|
|
|
189
216
|
<svelte:component this={PopupMenu} bind:menushown bind:menuid bind:hilited={popuphilited} bind:value={popupvalue} align='bottomleft'
|
|
190
217
|
{usePortal} {loading} {emptyText} {gap}
|
|
191
|
-
{menuContainerClass} {menuClass} {menuItemClass} {menuItemHilitedClass} {
|
|
218
|
+
{menuContainerClass} {menuClass} {menuItemClass} {menuItemHilitedClass} {menuCategoryClass}
|
|
192
219
|
items={options} buttonelement={inputelement}
|
|
193
220
|
on:change={addSelection}/>
|
|
194
221
|
|
|
@@ -257,4 +284,9 @@ onMount(() => {
|
|
|
257
284
|
border: var(--multiselect-pill-selected-border, 1px solid transparent);
|
|
258
285
|
color: var(--multiselect-pill-selected-text, white);
|
|
259
286
|
}
|
|
287
|
+
.delete-all-container {
|
|
288
|
+
width: 100%;
|
|
289
|
+
display: flex;
|
|
290
|
+
justify-content: flex-end;
|
|
291
|
+
}
|
|
260
292
|
</style>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { SvelteComponentTyped } from "svelte";
|
|
2
2
|
import DefaultPopupMenu from './PopupMenu.svelte';
|
|
3
|
-
import type { PopupMenuItem
|
|
3
|
+
import type { PopupMenuItem } from '../types';
|
|
4
4
|
declare const __propDef: {
|
|
5
5
|
props: {
|
|
6
6
|
name: string;
|
|
7
7
|
/**
|
|
8
8
|
* Function to pass to the component that tells it how to use the text in the text input to determine what
|
|
9
9
|
* `PopupMenuItem[]` should be displayed in the `PopupMenu`. Items already 'selected' from the popup menu will
|
|
10
|
-
* be tracked and automatically filtered from the popup if returned as one of the `PopupMenuItem[]` by `getOptions`. */ getOptions: (search: string) => Promise<
|
|
10
|
+
* be tracked and automatically filtered from the popup if returned as one of the `PopupMenuItem[]` by `getOptions`. */ getOptions: (search: string) => Promise<PopupMenuItem[]> | PopupMenuItem[];
|
|
11
11
|
id?: string;
|
|
12
12
|
disabled?: boolean;
|
|
13
13
|
menuContainerClass?: string;
|
|
@@ -15,7 +15,7 @@ declare const __propDef: {
|
|
|
15
15
|
menuItemClass?: string;
|
|
16
16
|
menuItemHilitedClass?: string;
|
|
17
17
|
inputClass?: string;
|
|
18
|
-
|
|
18
|
+
menuCategoryClass?: string;
|
|
19
19
|
/** The maximum number of selections allowed before making new selections is disabled. Default of 0 is unlimited. */ maxSelections?: number;
|
|
20
20
|
/** Consuming components may need to make decisions about what to display or return as options in the popup based
|
|
21
21
|
* on what's already selected. This component property can be used to both pass an initial set of selections but
|
|
@@ -26,6 +26,10 @@ declare const __propDef: {
|
|
|
26
26
|
usePortal?: HTMLElement | true | undefined;
|
|
27
27
|
descid?: string | undefined;
|
|
28
28
|
/** You can define your own PopupMenu and pass for that to be used or accept DefaultPopupMenu. */ PopupMenu?: typeof DefaultPopupMenu;
|
|
29
|
+
/** Add a Delete All button to clear all selected items */ includeDeleteAll?: boolean;
|
|
30
|
+
/** Show a confirmation message before clearing all selections */ confirmDelete?: string | undefined;
|
|
31
|
+
/** A function that can be used to customize the labels on the selected item pills. Default is a function
|
|
32
|
+
* that takes a PopupMenuItem and returns its label, if available, or its value when no label is provided. */ selectedItemLabel?: (item: PopupMenuItem) => string;
|
|
29
33
|
};
|
|
30
34
|
events: {
|
|
31
35
|
change: CustomEvent<any>;
|
|
@@ -35,6 +39,7 @@ declare const __propDef: {
|
|
|
35
39
|
[evt: string]: CustomEvent<any>;
|
|
36
40
|
};
|
|
37
41
|
slots: {
|
|
42
|
+
deleteall: {};
|
|
38
43
|
default: {};
|
|
39
44
|
};
|
|
40
45
|
};
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
The purpose of [`PopupMenu`](https://github.com/txstate-etc/svelte-components/blob/main/docs/PopupMenu.md) is to display a menu of options to the user, activated by clicking any element
|
|
3
3
|
bound to `buttonelement`. It can also be controlled from the parent, by binding the `menushown` boolean.
|
|
4
4
|
-->
|
|
5
|
-
<script lang="ts">import { createEventDispatcher, onDestroy, tick } from 'svelte';
|
|
6
|
-
import {
|
|
5
|
+
<script lang="ts" generics="MenuItem extends PopupMenuItem = PopupMenuItem">import { createEventDispatcher, onDestroy, tick } from 'svelte';
|
|
6
|
+
import { groupby, isNull, 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';
|
|
@@ -60,22 +60,40 @@ export let menuClass = '';
|
|
|
60
60
|
export let menuItemClass = '';
|
|
61
61
|
export let menuItemHilitedClass = '';
|
|
62
62
|
export let menuItemSelectedClass = '';
|
|
63
|
-
export let
|
|
63
|
+
export let menuCategoryClass = '';
|
|
64
64
|
export let hideSelectedIndicator = false;
|
|
65
65
|
export let hideEmptyText = false;
|
|
66
|
+
/** If true, the role for the popup menu will be 'menu' instead of the default 'listbox' value */
|
|
66
67
|
export let usemenurole = false;
|
|
67
68
|
let menuelement;
|
|
68
69
|
const itemelements = [];
|
|
69
70
|
let firstactive = 0;
|
|
70
71
|
let lastactive = items.length - 1;
|
|
71
|
-
$: hasMeaningfulItems = showSelected ? !!items.
|
|
72
|
+
$: hasMeaningfulItems = showSelected ? !!items.length : !!items.filter(itm => itm.value !== value).length;
|
|
72
73
|
function hiddenItem(item) {
|
|
73
74
|
return !!item && !showSelected && item.value === value;
|
|
74
75
|
}
|
|
76
|
+
$: grouped = groupby(items, item => item.group ?? 'default-group');
|
|
77
|
+
let itemsOrderedByGroup = [];
|
|
78
|
+
function getItemIndex(item) {
|
|
79
|
+
return itemsOrderedByGroup.findIndex(itm => {
|
|
80
|
+
// need to handle default group options
|
|
81
|
+
if (itm.value === item.value) {
|
|
82
|
+
if (item.group === 'default-group') {
|
|
83
|
+
return isNull(itm.group);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
return item.group === itm.group;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
75
92
|
async function reactToItems(..._) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
93
|
+
itemsOrderedByGroup = Object.values(grouped).flat();
|
|
94
|
+
firstactive = itemsOrderedByGroup.findIndex(itm => 'value' in itm && !itm.disabled && !hiddenItem(itm));
|
|
95
|
+
lastactive = itemsOrderedByGroup.length - [...itemsOrderedByGroup].reverse().findIndex(itm => 'value' in itm && !itm.disabled && !hiddenItem(itm)) - 1;
|
|
96
|
+
if (hilited && (itemsOrderedByGroup[hilited])?.disabled)
|
|
79
97
|
hilited = firstactive;
|
|
80
98
|
}
|
|
81
99
|
$: void reactToItems(items, value);
|
|
@@ -107,13 +125,11 @@ $: void reactToMenuShown(menushown);
|
|
|
107
125
|
function move(idx) {
|
|
108
126
|
if (!menushown)
|
|
109
127
|
return;
|
|
110
|
-
|
|
111
|
-
idx++;
|
|
112
|
-
if (items[idx] == null || items[idx]?.disabled)
|
|
128
|
+
if (itemsOrderedByGroup[idx] == null || (itemsOrderedByGroup[idx])?.disabled)
|
|
113
129
|
return;
|
|
114
130
|
hilited = Math.max(firstactive, Math.min(lastactive, idx));
|
|
115
131
|
itemelements[hilited].scrollIntoView({ block: 'center' });
|
|
116
|
-
buttonelement
|
|
132
|
+
buttonelement?.setAttribute('aria-activedescendant', `${menuid}-${hilited}`);
|
|
117
133
|
}
|
|
118
134
|
function isSelectable(itm) {
|
|
119
135
|
return 'value' in itm && !itm.disabled && !hiddenItem(itm);
|
|
@@ -125,8 +141,8 @@ function onkeydown(e) {
|
|
|
125
141
|
e.preventDefault();
|
|
126
142
|
if (menushown) {
|
|
127
143
|
let i = (hilited ?? firstactive - 1) + 1;
|
|
128
|
-
while (
|
|
129
|
-
i
|
|
144
|
+
while (itemsOrderedByGroup[i] && !isSelectable(itemsOrderedByGroup[i]))
|
|
145
|
+
i += 1;
|
|
130
146
|
move(i);
|
|
131
147
|
}
|
|
132
148
|
else {
|
|
@@ -138,8 +154,8 @@ function onkeydown(e) {
|
|
|
138
154
|
e.preventDefault();
|
|
139
155
|
if (menushown) {
|
|
140
156
|
let i = (hilited ?? lastactive + 1) - 1;
|
|
141
|
-
while (
|
|
142
|
-
i
|
|
157
|
+
while (itemsOrderedByGroup[i] && !isSelectable(itemsOrderedByGroup[i]))
|
|
158
|
+
i -= 1;
|
|
143
159
|
move(i);
|
|
144
160
|
}
|
|
145
161
|
else {
|
|
@@ -164,8 +180,8 @@ function onkeydown(e) {
|
|
|
164
180
|
if (menushown) {
|
|
165
181
|
menushown = false;
|
|
166
182
|
if (typeof hilited !== 'undefined') {
|
|
167
|
-
value =
|
|
168
|
-
dispatch('change',
|
|
183
|
+
value = (itemsOrderedByGroup[hilited])?.value;
|
|
184
|
+
dispatch('change', itemsOrderedByGroup[hilited]);
|
|
169
185
|
}
|
|
170
186
|
}
|
|
171
187
|
else {
|
|
@@ -180,23 +196,19 @@ function onkeydown(e) {
|
|
|
180
196
|
if (typeof hilited !== 'undefined') {
|
|
181
197
|
e.preventDefault();
|
|
182
198
|
menushown = false;
|
|
183
|
-
value =
|
|
184
|
-
dispatch('change',
|
|
199
|
+
value = (itemsOrderedByGroup[hilited])?.value;
|
|
200
|
+
dispatch('change', itemsOrderedByGroup[hilited]);
|
|
185
201
|
}
|
|
186
|
-
else {
|
|
187
|
-
if (buttonelement.tagName !== 'INPUT') {
|
|
188
|
-
e.preventDefault();
|
|
189
|
-
menushown = false;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
if (buttonelement.tagName !== 'INPUT') {
|
|
202
|
+
else if (buttonelement?.tagName !== 'INPUT') {
|
|
195
203
|
e.preventDefault();
|
|
196
|
-
menushown =
|
|
197
|
-
tick().then(() => { move(firstactive); }).catch(console.error);
|
|
204
|
+
menushown = false;
|
|
198
205
|
}
|
|
199
206
|
}
|
|
207
|
+
else if (buttonelement?.tagName !== 'INPUT') {
|
|
208
|
+
e.preventDefault();
|
|
209
|
+
menushown = true;
|
|
210
|
+
tick().then(() => { move(firstactive); }).catch(console.error);
|
|
211
|
+
}
|
|
200
212
|
}
|
|
201
213
|
else if (menushown && e.key === 'Escape') {
|
|
202
214
|
e.preventDefault();
|
|
@@ -247,13 +259,16 @@ function reactToButtonElement(buttonelement) {
|
|
|
247
259
|
if (buttonelement) {
|
|
248
260
|
buttonelement.setAttribute('aria-haspopup', (usemenurole ? 'true' : 'listbox'));
|
|
249
261
|
buttonelement.setAttribute('aria-expanded', menushown ? 'true' : 'false');
|
|
262
|
+
if (!usemenurole) {
|
|
263
|
+
buttonelement.setAttribute('role', 'combobox');
|
|
264
|
+
}
|
|
250
265
|
buttonelement.addEventListener('click', onbuttonclick);
|
|
251
266
|
buttonelement.addEventListener('keydown', onkeydown);
|
|
252
267
|
buttonelement.addEventListener('blur', onblur);
|
|
253
268
|
buttonelement.addEventListener('input', oninput);
|
|
254
269
|
const parents = getScrollParents(buttonelement);
|
|
255
270
|
for (const parent of [...parents, undefined]) {
|
|
256
|
-
const observer = new IntersectionObserver(
|
|
271
|
+
const observer = new IntersectionObserver(entries => {
|
|
257
272
|
for (const entry of entries) {
|
|
258
273
|
if (entry.intersectionRatio < 0.1)
|
|
259
274
|
menushown = false;
|
|
@@ -278,41 +293,62 @@ $: hasSelected = showSelected && !hideSelectedIndicator && items.some(itm => 'va
|
|
|
278
293
|
</script>
|
|
279
294
|
|
|
280
295
|
{#if menushown && !loading && (hasMeaningfulItems || !hideEmptyText)}
|
|
281
|
-
<div use:portal={usePortal === true ? undefined : (usePortal
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
{:else
|
|
304
|
-
|
|
305
|
-
|
|
296
|
+
<div use:portal={usePortal === true ? undefined : (usePortal ?? null)} use:glue={{ target: buttonelement, align, cover, gap, adjustparentheight, store: computedalign }} class={menuContainerClass}>
|
|
297
|
+
<ul bind:this={menuelement} id={menuid} role={usemenurole ? 'menu' : 'listbox'} tabindex="-1" style={width ? `width: ${width}` : ''} class={menuClass} class:hasSelected class:defaultmenu={!menuClass && !menuContainerClass} on:keydown={onkeydown}>
|
|
298
|
+
{#each Object.entries(grouped) as [group, groupitems] (group)}
|
|
299
|
+
{#if group === 'default-group'}
|
|
300
|
+
{#each groupitems as item, i (item.value)}
|
|
301
|
+
{@const itemIndex = getItemIndex(item)}
|
|
302
|
+
{#if (showSelected || item.value !== value)}
|
|
303
|
+
<li
|
|
304
|
+
id={`${menuid}-${itemIndex}`}
|
|
305
|
+
bind:this={itemelements[itemIndex]}
|
|
306
|
+
class={`${menuItemClass} ${itemIndex === hilited ? menuItemHilitedClass : ''} ${value === item.value ? menuItemSelectedClass : ''}`}
|
|
307
|
+
class:disabled={!!item.disabled}
|
|
308
|
+
class:hilited={!menuItemHilitedClass && itemIndex === hilited}
|
|
309
|
+
class:selected={showSelected && !hideSelectedIndicator && value === item.value}
|
|
310
|
+
on:click={onclick(item)}
|
|
311
|
+
role={usemenurole ? 'menuitem' : 'option'}
|
|
312
|
+
tabindex=-1
|
|
313
|
+
aria-selected={usemenurole ? undefined : (value === item.value)}
|
|
314
|
+
aria-disabled={item.disabled}
|
|
315
|
+
><slot {item} label={item.label || item.value} hilited={itemIndex === hilited} selected={value === item.value}>{item.label || item.value}</slot></li>
|
|
316
|
+
{/if}
|
|
317
|
+
{/each}
|
|
318
|
+
{:else}
|
|
319
|
+
<li role="group" aria-labelledby="{menuid}-{group}" class="group">
|
|
320
|
+
<span id="{menuid}-{group}" class="category {menuCategoryClass}">{group}</span>
|
|
321
|
+
<ul role="presentation">
|
|
322
|
+
{#each groupitems as item, i (item.value)}
|
|
323
|
+
{@const itemIndex = getItemIndex(item)}
|
|
324
|
+
{#if (showSelected || item.value !== value)}
|
|
325
|
+
<li
|
|
326
|
+
id={`${menuid}-${itemIndex}`}
|
|
327
|
+
bind:this={itemelements[itemIndex]}
|
|
328
|
+
class={`${menuItemClass} ${itemIndex === hilited ? menuItemHilitedClass : ''} ${value === item.value ? menuItemSelectedClass : ''}`}
|
|
329
|
+
class:disabled={!!item.disabled}
|
|
330
|
+
class:hilited={!menuItemHilitedClass && itemIndex === hilited}
|
|
331
|
+
class:selected={showSelected && !hideSelectedIndicator && value === item.value}
|
|
332
|
+
on:click={onclick(item)}
|
|
333
|
+
role={usemenurole ? 'menuitem' : 'option'}
|
|
334
|
+
tabindex=-1
|
|
335
|
+
aria-selected={usemenurole ? undefined : (value === item.value)}
|
|
336
|
+
aria-disabled={item.disabled}
|
|
337
|
+
><slot {item} label={item.label || item.value} hilited={itemIndex === hilited} selected={value === item.value}>{item.label || item.value}</slot></li>
|
|
338
|
+
{/if}
|
|
339
|
+
{/each}
|
|
340
|
+
</ul>
|
|
341
|
+
</li>
|
|
306
342
|
{/if}
|
|
307
343
|
{/each}
|
|
308
344
|
{#if !hasMeaningfulItems}
|
|
309
345
|
<li role={usemenurole ? 'menuitem' : 'option'} class={`${menuItemClass} disabled`} aria-live="assertive" aria-selected={usemenurole ? undefined : false}>
|
|
310
346
|
<slot name="noresults">
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
347
|
+
{#if !emptyText}
|
|
348
|
+
<span aria-hidden="true">{'¯\\_(ツ)_/¯'}</span><ScreenReaderOnly>{emptyText || 'no results found'}</ScreenReaderOnly>
|
|
349
|
+
{:else}
|
|
350
|
+
{emptyText}
|
|
351
|
+
{/if}
|
|
316
352
|
</slot>
|
|
317
353
|
</li>
|
|
318
354
|
{/if}
|
|
@@ -389,4 +425,11 @@ $: hasSelected = showSelected && !hideSelectedIndicator && items.some(itm => 'va
|
|
|
389
425
|
border-right: 0.2em solid;
|
|
390
426
|
box-sizing: border-box;
|
|
391
427
|
}
|
|
428
|
+
span.category {
|
|
429
|
+
display: block;
|
|
430
|
+
font-weight: bold;
|
|
431
|
+
}
|
|
432
|
+
.group ul {
|
|
433
|
+
padding-inline-start: 20px;
|
|
434
|
+
}
|
|
392
435
|
</style>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { SvelteComponentTyped } from "svelte";
|
|
2
2
|
import { Store } from '@txstate-mws/svelte-store';
|
|
3
3
|
import type { GlueAlignOpts, GlueAlignStore } from '../actions';
|
|
4
|
-
import type { PopupMenuItem
|
|
5
|
-
declare
|
|
6
|
-
props: {
|
|
4
|
+
import type { PopupMenuItem } from '../types';
|
|
5
|
+
declare class __sveltets_Render<MenuItem extends PopupMenuItem = PopupMenuItem> {
|
|
6
|
+
props(): {
|
|
7
7
|
/** The DOM element that will act as the "button" for this menu. The menu will be placed next to the
|
|
8
8
|
button and be controlled by the button. Keyboard access will be enabled when the button has focus.
|
|
9
|
-
This component adds all appropriate attributes (tabindex, roles, and aria) automatically. */ buttonelement: HTMLElement;
|
|
10
|
-
/** The list of menu items to be shown. Parent may change this at any time based on user activity. */ items?:
|
|
9
|
+
This component adds all appropriate attributes (tabindex, roles, and aria) automatically. */ buttonelement: HTMLElement | undefined;
|
|
10
|
+
/** The list of menu items to be shown. Parent may change this at any time based on user activity. */ items?: MenuItem[] | undefined;
|
|
11
11
|
menushown?: boolean;
|
|
12
12
|
value?: string | undefined;
|
|
13
13
|
/** Control where the menu will appear. Default is to use the current viewport to make a decision to
|
|
@@ -41,31 +41,31 @@ declare const __propDef: {
|
|
|
41
41
|
menuItemClass?: string;
|
|
42
42
|
menuItemHilitedClass?: string;
|
|
43
43
|
menuItemSelectedClass?: string;
|
|
44
|
-
|
|
44
|
+
menuCategoryClass?: string;
|
|
45
45
|
hideSelectedIndicator?: boolean;
|
|
46
46
|
hideEmptyText?: boolean;
|
|
47
|
-
usemenurole?: boolean;
|
|
47
|
+
/** If true, the role for the popup menu will be 'menu' instead of the default 'listbox' value */ usemenurole?: boolean;
|
|
48
48
|
};
|
|
49
|
-
|
|
49
|
+
events(): {
|
|
50
|
+
change: CustomEvent<MenuItem>;
|
|
51
|
+
};
|
|
52
|
+
slots(): {
|
|
50
53
|
default: {
|
|
51
|
-
item:
|
|
52
|
-
label:
|
|
54
|
+
item: MenuItem;
|
|
55
|
+
label: string;
|
|
53
56
|
hilited: boolean;
|
|
54
57
|
selected: boolean;
|
|
55
58
|
};
|
|
56
59
|
noresults: {};
|
|
57
60
|
};
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
export type PopupMenuProps = typeof __propDef.props;
|
|
63
|
-
export type PopupMenuEvents = typeof __propDef.events;
|
|
64
|
-
export type PopupMenuSlots = typeof __propDef.slots;
|
|
61
|
+
}
|
|
62
|
+
export type PopupMenuProps<MenuItem extends PopupMenuItem = PopupMenuItem> = ReturnType<__sveltets_Render<MenuItem>['props']>;
|
|
63
|
+
export type PopupMenuEvents<MenuItem extends PopupMenuItem = PopupMenuItem> = ReturnType<__sveltets_Render<MenuItem>['events']>;
|
|
64
|
+
export type PopupMenuSlots<MenuItem extends PopupMenuItem = PopupMenuItem> = ReturnType<__sveltets_Render<MenuItem>['slots']>;
|
|
65
65
|
/**
|
|
66
66
|
* The purpose of [`PopupMenu`](https://github.com/txstate-etc/svelte-components/blob/main/docs/PopupMenu.md) is to display a menu of options to the user, activated by clicking any element
|
|
67
67
|
* bound to `buttonelement`. It can also be controlled from the parent, by binding the `menushown` boolean.
|
|
68
68
|
*/
|
|
69
|
-
export default class PopupMenu extends SvelteComponentTyped<PopupMenuProps
|
|
69
|
+
export default class PopupMenu<MenuItem extends PopupMenuItem = PopupMenuItem> extends SvelteComponentTyped<PopupMenuProps<MenuItem>, PopupMenuEvents<MenuItem>, PopupMenuSlots<MenuItem>> {
|
|
70
70
|
}
|
|
71
71
|
export {};
|
|
@@ -7,8 +7,12 @@ export interface PopupMenuItem {
|
|
|
7
7
|
/** Toggles the choice as one that's visible but is disabled and unclickable until conditions
|
|
8
8
|
* are met that would cause it to become enabled. */
|
|
9
9
|
disabled?: boolean;
|
|
10
|
+
/** Allows PopupMenuItems to be grouped by categories in the menu */
|
|
11
|
+
group?: string;
|
|
10
12
|
}
|
|
11
|
-
/** A PopupMenu list native type for displaying
|
|
13
|
+
/** A PopupMenu list native type for displaying
|
|
14
|
+
* @note Not currently in use
|
|
15
|
+
*/
|
|
12
16
|
export interface PopupMenuDivider {
|
|
13
17
|
/** Toggle for whether this instance of this type is shown. */
|
|
14
18
|
divider: boolean;
|
|
@@ -23,7 +27,7 @@ export interface PopupMenuDivider {
|
|
|
23
27
|
* @note Not currently in use. */
|
|
24
28
|
export interface PopupMenuGroup {
|
|
25
29
|
/** The label for the group. */
|
|
26
|
-
|
|
30
|
+
label: string;
|
|
27
31
|
/** The listing of options that would be listed under the parent `<ul>`. */
|
|
28
32
|
options: PopupMenuTypes[];
|
|
29
33
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export * from './CardLayout.js';
|
|
2
|
-
export * from './CollapsingTable.js';
|
|
3
|
-
export * from './PopupMenu.js';
|
|
2
|
+
export type * from './CollapsingTable.js';
|
|
3
|
+
export type * from './PopupMenu.js';
|
package/dist/types/index.js
CHANGED
package/dist/util/bodyoffset.js
CHANGED
|
@@ -44,7 +44,7 @@ function getTranslate(element) {
|
|
|
44
44
|
}
|
|
45
45
|
// Can either be 2d or 3d transform
|
|
46
46
|
const matrixType = matrix.includes('3d') ? '3d' : '2d';
|
|
47
|
-
const matrixValues = matrix.match(/matrix.*\((.+)\)/)[1].split(', ');
|
|
47
|
+
const matrixValues = matrix.match(/matrix.*\((.+)\)/v)[1].split(', ');
|
|
48
48
|
// 2d matrices have 6 values
|
|
49
49
|
// Last 2 values are X and Y.
|
|
50
50
|
// 2d matrices does not have Z value.
|
package/dist/util/util.js
CHANGED
|
@@ -5,7 +5,6 @@ export function debounced(fn, timeout) {
|
|
|
5
5
|
let timer;
|
|
6
6
|
return (...args) => {
|
|
7
7
|
clearTimeout(timer);
|
|
8
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
9
8
|
timer = setTimeout(() => fn(...args), timeout);
|
|
10
9
|
};
|
|
11
10
|
}
|
|
@@ -25,7 +24,7 @@ export function getScrollParents(element) {
|
|
|
25
24
|
if (excludeStaticParent && style.position === 'static') {
|
|
26
25
|
continue;
|
|
27
26
|
}
|
|
28
|
-
if (/(auto|scroll|hidden)
|
|
27
|
+
if (/(auto|scroll|hidden)/v.test(style.overflow + style.overflowY + style.overflowX))
|
|
29
28
|
return [parent, ...getScrollParents(parent)];
|
|
30
29
|
}
|
|
31
30
|
return [document.body];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@txstate-mws/svelte-components",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.12",
|
|
4
4
|
"description": "Svelte components that are generically useful.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"prepublishOnly": "svelte-package",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"preview": "vite preview",
|
|
11
11
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
12
12
|
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
|
13
|
-
"lint": "eslint
|
|
13
|
+
"lint": "eslint .",
|
|
14
14
|
"test": "playwright test tests"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
@@ -20,16 +20,18 @@
|
|
|
20
20
|
"txstate-utils": "^1.7.12"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
+
"@eslint/compat": "^2.0.3",
|
|
23
24
|
"@playwright/test": "^1.18.1",
|
|
24
|
-
"@
|
|
25
|
+
"@stylistic/eslint-plugin": "^2.10.1",
|
|
26
|
+
"@sveltejs/adapter-auto": "^7.0.0",
|
|
25
27
|
"@sveltejs/kit": "^2.0.0",
|
|
26
28
|
"@sveltejs/package": "^2.0.2",
|
|
27
|
-
"@types/tabbable": "^
|
|
28
|
-
"eslint-config-
|
|
29
|
-
"eslint-plugin-svelte": "^
|
|
30
|
-
"svelte
|
|
31
|
-
"svelte-
|
|
32
|
-
"
|
|
29
|
+
"@types/tabbable": "^5.0.0",
|
|
30
|
+
"eslint-config-love": "^151.0.0",
|
|
31
|
+
"eslint-plugin-svelte": "^3.0.0",
|
|
32
|
+
"svelte": "^5.0.0",
|
|
33
|
+
"svelte-check": "^4.0.0",
|
|
34
|
+
"svelte-preprocess": "^6.0.0",
|
|
33
35
|
"tslib": "^2.3.1",
|
|
34
36
|
"typescript": "^5.0.4"
|
|
35
37
|
},
|
|
@@ -46,7 +48,9 @@
|
|
|
46
48
|
},
|
|
47
49
|
"./package.json": "./package.json"
|
|
48
50
|
},
|
|
49
|
-
"files": [
|
|
51
|
+
"files": [
|
|
52
|
+
"dist"
|
|
53
|
+
],
|
|
50
54
|
"repository": {
|
|
51
55
|
"type": "git",
|
|
52
56
|
"url": "git@github.com:txstate-etc/svelte-components.git"
|