@txstate-mws/svelte-components 1.0.4 → 1.1.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/.eslintrc.cjs +37 -0
- package/docs/CollapsingTable.md +79 -0
- package/docs/ConditionalWrapper.md +35 -0
- package/docs/Modal.md +63 -0
- package/docs/PopupMenu.md +31 -0
- package/docs/ScreenReaderOnly.md +10 -0
- package/docs/actions.md +84 -0
- package/docs/stores.md +203 -0
- package/package.json +15 -37
- package/playwright.config.js +112 -0
- package/src/app.html +14 -0
- package/src/global.d.ts +1 -0
- package/src/lib/actions/buttonify.ts +27 -0
- package/src/lib/actions/glue.ts +96 -0
- package/src/lib/actions/index.ts +7 -0
- package/src/lib/actions/offset.ts +43 -0
- package/src/lib/actions/portal.ts +13 -0
- package/src/lib/actions/resize.ts +72 -0
- package/src/lib/actions/sticky.ts +105 -0
- package/src/lib/actions/stickyfixed.ts +160 -0
- package/src/lib/components/Card.svelte +25 -0
- package/src/lib/components/CardLayout.svelte +188 -0
- package/{components → src/lib/components}/CollapsingTable.svelte +65 -63
- package/{components → src/lib/components}/ConditionalWrapper.svelte +7 -6
- package/src/lib/components/FocusLock.svelte +102 -0
- package/{components → src/lib/components}/Modal.svelte +24 -22
- package/src/lib/components/MultiSelect.svelte +174 -0
- package/src/lib/components/PopupMenu.svelte +285 -0
- package/{components → src/lib/components}/ScreenReaderOnly.svelte +4 -3
- package/src/lib/components/index.ts +9 -0
- package/src/lib/index.ts +4 -0
- package/src/lib/types/CardLayout.ts +14 -0
- package/src/lib/types/CollapsingTable.ts +8 -0
- package/src/lib/types/PopupMenu.ts +5 -0
- package/src/lib/types/index.ts +3 -0
- package/src/lib/util/bodyoffset.ts +77 -0
- package/src/lib/util/index.ts +3 -0
- package/src/lib/util/repositioned.ts +91 -0
- package/src/lib/util/util.ts +36 -0
- package/src/routes/__layout.svelte +125 -0
- package/src/routes/actions/index.svelte +1 -0
- package/src/routes/actions/offset.svelte +4 -0
- package/src/routes/actions/offsetdebounce.svelte +4 -0
- package/src/routes/actions/resize.svelte +4 -0
- package/src/routes/actions/resizedebounce.svelte +4 -0
- package/src/routes/actions/sticky.svelte +4 -0
- package/src/routes/cards/index.svelte +4 -0
- package/src/routes/cards/preserve.svelte +4 -0
- package/src/routes/collapsingtable/index.svelte +4 -0
- package/src/routes/collapsingtable/stickyheader.svelte +4 -0
- package/src/routes/index.svelte +2 -0
- package/src/routes/modal.svelte +4 -0
- package/src/routes/multiselect.svelte +4 -0
- package/src/routes/popupmenu/body.svelte +4 -0
- package/src/routes/popupmenu/complex.svelte +4 -0
- package/src/routes/popupmenu/empty.svelte +5 -0
- package/src/routes/popupmenu/index.svelte +4 -0
- package/src/routes/popupmenu/parent.svelte +4 -0
- package/src/routes/popupmenu/scrollable.svelte +27 -0
- package/src/routes/popupmenu/showselected.svelte +4 -0
- package/src/test/Cards.svelte +64 -0
- package/src/test/CollapsingTableTest.svelte +36 -0
- package/src/test/ModalTest.svelte +58 -0
- package/src/test/MultiSelectTest.svelte +26 -0
- package/src/test/Offset.svelte +32 -0
- package/src/test/PopupMenuBody.svelte +34 -0
- package/src/test/PopupMenuButtonIsParent.svelte +36 -0
- package/src/test/PopupMenuComplex.svelte +34 -0
- package/src/test/PopupMenuSimple.svelte +36 -0
- package/src/test/Resize.svelte +29 -0
- package/src/test/Sticky.svelte +65 -0
- package/static/layout.css +263 -0
- package/svelte.config.js +15 -0
- package/tests/.eslintrc.cjs +19 -0
- package/tests/CardLayout.test.js +41 -0
- package/tests/CollapsingTable.test.js +37 -0
- package/tests/Modal.test.js +18 -0
- package/tests/MultiSelect.test.js +58 -0
- package/tests/PopupMenu.test.js +55 -0
- package/tests/common.js +4 -0
- package/tsconfig.json +31 -0
- package/actions/buttonify.d.ts +0 -8
- package/actions/buttonify.js +0 -26
- package/actions/glue.d.ts +0 -16
- package/actions/glue.js +0 -82
- package/actions/index.d.ts +0 -7
- package/actions/index.js +0 -7
- package/actions/offset.d.ts +0 -15
- package/actions/offset.js +0 -35
- package/actions/portal.d.ts +0 -4
- package/actions/portal.js +0 -13
- package/actions/resize.d.ts +0 -20
- package/actions/resize.js +0 -45
- package/actions/sticky.d.ts +0 -33
- package/actions/sticky.js +0 -85
- package/actions/stickyfixed.d.ts +0 -25
- package/actions/stickyfixed.js +0 -157
- package/components/CollapsingTable.svelte.d.ts +0 -59
- package/components/ConditionalWrapper.svelte.d.ts +0 -23
- package/components/FocusLock.svelte +0 -107
- package/components/FocusLock.svelte.d.ts +0 -26
- package/components/Modal.svelte.d.ts +0 -27
- package/components/PopupMenu.svelte +0 -279
- package/components/PopupMenu.svelte.d.ts +0 -44
- package/components/ScreenReaderOnly.svelte.d.ts +0 -20
- package/components/index.d.ts +0 -6
- package/components/index.js +0 -6
- package/index.d.ts +0 -4
- package/index.js +0 -4
- package/types/CollapsingTable.d.ts +0 -8
- package/types/CollapsingTable.js +0 -1
- package/types/PopupMenu.d.ts +0 -5
- package/types/PopupMenu.js +0 -1
- package/types/index.d.ts +0 -2
- package/types/index.js +0 -2
- package/util/bodyoffset.d.ts +0 -14
- package/util/bodyoffset.js +0 -72
- package/util/index.d.ts +0 -3
- package/util/index.js +0 -3
- package/util/repositioned.d.ts +0 -12
- package/util/repositioned.js +0 -81
- package/util/util.d.ts +0 -2
- package/util/util.js +0 -10
package/actions/stickyfixed.js
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { bodyOffset, watchForPositionChange } from '../util/index.js';
|
|
2
|
-
/**
|
|
3
|
-
* An implementation of stickyness based on `positon: fixed`. Only use when
|
|
4
|
-
* `position: sticky` won't work for you. If `position: sticky` does work for you, use it,
|
|
5
|
-
* it performs better.
|
|
6
|
-
*
|
|
7
|
-
* Reasons `position: sticky` might not work:
|
|
8
|
-
* 1) it cannot work on a thead or tr, only td and th (and non-table elements)
|
|
9
|
-
* 2) it gets confused if any parent elements have an `overflow` set, even `overflow: hidden`
|
|
10
|
-
* 3) it always stays within the boundaries of the closest parent element that has a `position`
|
|
11
|
-
* * use:sticky accepts a `target` element as a parameter, defining the bounding container
|
|
12
|
-
*
|
|
13
|
-
* Note that this implementation gets great animation performance at the cost of extra complexity.
|
|
14
|
-
* It does a lot of work like cloning the element you place it on, placing the cloned element next
|
|
15
|
-
* to yours in the DOM, setting widths to match between them, watching for mutations to keep them
|
|
16
|
-
* in sync, etc. Some of this work may get foiled if you have a complex html/css/js situation.
|
|
17
|
-
*
|
|
18
|
-
* You may also want to try use:sticky. It is based on setting `transform: translateY()` as appropriate,
|
|
19
|
-
* and is less fragile than this. However, it cannot do its work before the scroll on mobile, only after,
|
|
20
|
-
* so it will visually lag a bit on mobile.
|
|
21
|
-
*/
|
|
22
|
-
export function stickyfixed(el, config) {
|
|
23
|
-
let floating;
|
|
24
|
-
let translateY;
|
|
25
|
-
let scrollstart = Number.MAX_SAFE_INTEGER;
|
|
26
|
-
let scrollend = Number.MAX_SAFE_INTEGER;
|
|
27
|
-
let spacer;
|
|
28
|
-
let elheight;
|
|
29
|
-
let left;
|
|
30
|
-
let top = 0;
|
|
31
|
-
function generatespacer(mutations) {
|
|
32
|
-
if (mutations?.every(m => m.attributeName === 'style'))
|
|
33
|
-
return;
|
|
34
|
-
spacer?.remove();
|
|
35
|
-
spacer = el.cloneNode(true);
|
|
36
|
-
spacer.setAttribute('aria-hidden', 'true');
|
|
37
|
-
spacer.style.position = '';
|
|
38
|
-
spacer.style.top = '';
|
|
39
|
-
spacer.style.left = '';
|
|
40
|
-
spacer.style.width = '';
|
|
41
|
-
const tds = Array.from(spacer.querySelectorAll('td,th'));
|
|
42
|
-
for (const td of tds)
|
|
43
|
-
td.style.width = '';
|
|
44
|
-
if (floating)
|
|
45
|
-
el.parentElement?.insertBefore(spacer, el);
|
|
46
|
-
}
|
|
47
|
-
const mutationobserver = new MutationObserver(generatespacer);
|
|
48
|
-
mutationobserver.observe(el, {
|
|
49
|
-
subtree: true,
|
|
50
|
-
childList: true,
|
|
51
|
-
attributes: true,
|
|
52
|
-
characterData: true
|
|
53
|
-
});
|
|
54
|
-
generatespacer();
|
|
55
|
-
function adjustwidths() {
|
|
56
|
-
const tds = Array.from(el.querySelectorAll('td,th'));
|
|
57
|
-
if (floating) {
|
|
58
|
-
const spacertds = Array.from(spacer.querySelectorAll('td,th'));
|
|
59
|
-
const widths = spacertds.map(td => window.getComputedStyle(td).width);
|
|
60
|
-
const table = spacer.closest('table');
|
|
61
|
-
let extrawidth = 0;
|
|
62
|
-
if (table) {
|
|
63
|
-
const cell = el.querySelector('th:last-child,td:last-child');
|
|
64
|
-
if (cell) {
|
|
65
|
-
const tstyle = window.getComputedStyle(table);
|
|
66
|
-
extrawidth = tstyle.borderCollapse === 'collapse' ? parseInt(window.getComputedStyle(cell).borderRightWidth) : 0;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
const width = `${parseInt(window.getComputedStyle(spacer).width) + extrawidth}px`;
|
|
70
|
-
elheight = spacer.offsetHeight;
|
|
71
|
-
for (let i = 0; i < tds.length; i++)
|
|
72
|
-
tds[i].style.width = widths[i];
|
|
73
|
-
el.style.width = width;
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
for (const td of tds)
|
|
77
|
-
td.style.width = '';
|
|
78
|
-
el.style.width = '';
|
|
79
|
-
elheight = el.offsetHeight;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
const resizeobserver = new ResizeObserver(adjustwidths);
|
|
83
|
-
function recalc(scroll) {
|
|
84
|
-
const newTranslateY = Math.max(Math.min(scroll - 1, scrollend) - scrollstart, 0);
|
|
85
|
-
const newFloating = newTranslateY > 0;
|
|
86
|
-
const newTop = Math.max(Math.min(scrollend - scroll, 0), -2 * elheight);
|
|
87
|
-
if (newFloating !== floating) {
|
|
88
|
-
floating = newFloating;
|
|
89
|
-
if (floating) {
|
|
90
|
-
el.parentElement?.insertBefore(spacer, el);
|
|
91
|
-
el.style.position = 'fixed';
|
|
92
|
-
el.style.top = `${Math.min(scrollend - scroll, 0)}px`;
|
|
93
|
-
el.style.left = `${left}px`;
|
|
94
|
-
resizeobserver.observe(spacer);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
el.style.position = '';
|
|
98
|
-
el.style.top = '';
|
|
99
|
-
el.style.left = '';
|
|
100
|
-
resizeobserver.disconnect();
|
|
101
|
-
spacer.remove();
|
|
102
|
-
}
|
|
103
|
-
adjustwidths();
|
|
104
|
-
}
|
|
105
|
-
if (newTranslateY !== translateY) {
|
|
106
|
-
translateY = newTranslateY;
|
|
107
|
-
config?.store?.update(v => ({ ...v, translateY, floating }));
|
|
108
|
-
}
|
|
109
|
-
if (top !== newTop) {
|
|
110
|
-
top = newTop;
|
|
111
|
-
if (floating)
|
|
112
|
-
el.style.top = `${top}px`;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
function onscroll(e) {
|
|
116
|
-
recalc(window.scrollY);
|
|
117
|
-
}
|
|
118
|
-
let lastOffsetParent = el.offsetParent;
|
|
119
|
-
function onpositionchange(offset) {
|
|
120
|
-
const offsetParent = el.offsetParent;
|
|
121
|
-
if (!(offsetParent instanceof HTMLElement))
|
|
122
|
-
return;
|
|
123
|
-
if (lastOffsetParent !== offsetParent) {
|
|
124
|
-
offset[0] = bodyOffset(offsetParent);
|
|
125
|
-
update(offsetParent, onpositionchange);
|
|
126
|
-
lastOffsetParent = offsetParent;
|
|
127
|
-
}
|
|
128
|
-
const table = el.closest('table');
|
|
129
|
-
const borderspacing = (table ? parseInt(window.getComputedStyle(table).borderSpacing) : 0);
|
|
130
|
-
scrollstart = offset[0].top + el.offsetTop;
|
|
131
|
-
left = offset[0].left + (spacer.parentElement ? spacer.offsetLeft : el.offsetLeft);
|
|
132
|
-
if (el.tagName === 'TR') {
|
|
133
|
-
scrollstart -= borderspacing;
|
|
134
|
-
}
|
|
135
|
-
const containeroffset = config?.target ? offset[1] ?? offset[0] : offset[0];
|
|
136
|
-
scrollend = Math.max(scrollstart, containeroffset.top + (config?.target ?? offsetParent).clientHeight - el.offsetHeight);
|
|
137
|
-
recalc(window.scrollY);
|
|
138
|
-
}
|
|
139
|
-
const { destroy, update } = watchForPositionChange([el.offsetParent, config?.target], onpositionchange);
|
|
140
|
-
window.addEventListener('scroll', onscroll, { passive: true });
|
|
141
|
-
function stickyUpdate(newConfig) {
|
|
142
|
-
if (newConfig?.store !== config?.store)
|
|
143
|
-
newConfig?.store?.update(v => ({ ...v, translateY, floating }));
|
|
144
|
-
config = newConfig;
|
|
145
|
-
update([el.offsetParent, config?.target], onpositionchange);
|
|
146
|
-
}
|
|
147
|
-
function stickyDestroy() {
|
|
148
|
-
resizeobserver.disconnect();
|
|
149
|
-
mutationobserver.disconnect();
|
|
150
|
-
window.removeEventListener('scroll', onscroll);
|
|
151
|
-
destroy();
|
|
152
|
-
}
|
|
153
|
-
return {
|
|
154
|
-
update: stickyUpdate,
|
|
155
|
-
destroy: stickyDestroy
|
|
156
|
-
};
|
|
157
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
import type { CollapsingTableColumn } from '../types';
|
|
3
|
-
import DefaultPopupMenu from './PopupMenu.svelte';
|
|
4
|
-
declare const __propDef: {
|
|
5
|
-
props: {
|
|
6
|
-
tableClass?: string;
|
|
7
|
-
bodyRowClass?: string;
|
|
8
|
-
bodyCellClass?: string;
|
|
9
|
-
headerRowClass?: string;
|
|
10
|
-
headerCellClass?: string;
|
|
11
|
-
menuContainerClass?: string;
|
|
12
|
-
menuClass?: string;
|
|
13
|
-
menuItemClass?: string;
|
|
14
|
-
menuItemHilitedClass?: string;
|
|
15
|
-
items: any[];
|
|
16
|
-
config?: CollapsingTableColumn[] | undefined;
|
|
17
|
-
defaultCellWidth?: number;
|
|
18
|
-
PopupMenu?: typeof DefaultPopupMenu;
|
|
19
|
-
slots?: Record<string, boolean>;
|
|
20
|
-
stickyheader?: boolean;
|
|
21
|
-
};
|
|
22
|
-
events: {
|
|
23
|
-
[evt: string]: CustomEvent<any>;
|
|
24
|
-
};
|
|
25
|
-
slots: {
|
|
26
|
-
headercell: {
|
|
27
|
-
column: CollapsingTableColumn & {
|
|
28
|
-
widthPercent?: number;
|
|
29
|
-
};
|
|
30
|
-
key: string;
|
|
31
|
-
title: string;
|
|
32
|
-
item: any;
|
|
33
|
-
value: any;
|
|
34
|
-
};
|
|
35
|
-
dropicon: {
|
|
36
|
-
column: CollapsingTableColumn & {
|
|
37
|
-
widthPercent?: number;
|
|
38
|
-
};
|
|
39
|
-
item: any;
|
|
40
|
-
value: any;
|
|
41
|
-
};
|
|
42
|
-
default: {
|
|
43
|
-
key: string;
|
|
44
|
-
value: any;
|
|
45
|
-
item: any;
|
|
46
|
-
};
|
|
47
|
-
footer: {
|
|
48
|
-
item: any;
|
|
49
|
-
key: any;
|
|
50
|
-
value: any;
|
|
51
|
-
};
|
|
52
|
-
};
|
|
53
|
-
};
|
|
54
|
-
export declare type CollapsingTableProps = typeof __propDef.props;
|
|
55
|
-
export declare type CollapsingTableEvents = typeof __propDef.events;
|
|
56
|
-
export declare type CollapsingTableSlots = typeof __propDef.slots;
|
|
57
|
-
export default class CollapsingTable extends SvelteComponentTyped<CollapsingTableProps, CollapsingTableEvents, CollapsingTableSlots> {
|
|
58
|
-
}
|
|
59
|
-
export {};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
declare const __propDef: {
|
|
3
|
-
props: {
|
|
4
|
-
[x: string]: any;
|
|
5
|
-
condition: boolean | undefined;
|
|
6
|
-
component?: Function | undefined;
|
|
7
|
-
a?: boolean;
|
|
8
|
-
span?: boolean;
|
|
9
|
-
element?: HTMLElement | undefined;
|
|
10
|
-
};
|
|
11
|
-
events: {
|
|
12
|
-
[evt: string]: CustomEvent<any>;
|
|
13
|
-
};
|
|
14
|
-
slots: {
|
|
15
|
-
default: {};
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
export declare type ConditionalWrapperProps = typeof __propDef.props;
|
|
19
|
-
export declare type ConditionalWrapperEvents = typeof __propDef.events;
|
|
20
|
-
export declare type ConditionalWrapperSlots = typeof __propDef.slots;
|
|
21
|
-
export default class ConditionalWrapper extends SvelteComponentTyped<ConditionalWrapperProps, ConditionalWrapperEvents, ConditionalWrapperSlots> {
|
|
22
|
-
}
|
|
23
|
-
export {};
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
<script context="module" >const FocusLockStack = [];
|
|
2
|
-
</script>
|
|
3
|
-
|
|
4
|
-
<script >export let returnfocusto = undefined;
|
|
5
|
-
export let initialfocus = undefined;
|
|
6
|
-
export let hidefocus = true;
|
|
7
|
-
export let hidefocuslabel = 'focus is above modal dialog, start tabbing';
|
|
8
|
-
export let escapable = true;
|
|
9
|
-
let className = '';
|
|
10
|
-
export { className as class };
|
|
11
|
-
import { onMount, onDestroy, createEventDispatcher, tick } from 'svelte';
|
|
12
|
-
import { tabbable } from 'tabbable';
|
|
13
|
-
import { sleep } from 'txstate-utils';
|
|
14
|
-
import { buttonify } from '../actions';
|
|
15
|
-
import ScreenReaderOnly from './ScreenReaderOnly.svelte';
|
|
16
|
-
const dispatch = createEventDispatcher();
|
|
17
|
-
let lockelement;
|
|
18
|
-
let abovelockelement;
|
|
19
|
-
let active = true;
|
|
20
|
-
let listenforescape = false;
|
|
21
|
-
if (initialfocus)
|
|
22
|
-
hidefocus = false;
|
|
23
|
-
onMount(async () => {
|
|
24
|
-
const prevFocusLock = FocusLockStack.slice(-1)[0];
|
|
25
|
-
if (prevFocusLock)
|
|
26
|
-
prevFocusLock.pause();
|
|
27
|
-
FocusLockStack.push({
|
|
28
|
-
pause: () => { active = false; },
|
|
29
|
-
unpause: () => { active = true; }
|
|
30
|
-
});
|
|
31
|
-
if (typeof returnfocusto === 'undefined') {
|
|
32
|
-
returnfocusto = document.querySelector(':focus');
|
|
33
|
-
}
|
|
34
|
-
await sleep(1); // sapper resets focus on page load, so we'll wait a millisecond in case our modal is on screen at load
|
|
35
|
-
if (!lockelement)
|
|
36
|
-
return; // in case we already unmounted
|
|
37
|
-
if (initialfocus) {
|
|
38
|
-
const focusEl = lockelement.querySelector(initialfocus);
|
|
39
|
-
if (focusEl instanceof HTMLElement)
|
|
40
|
-
focusEl.focus();
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
setInitialFocus();
|
|
44
|
-
}
|
|
45
|
-
setTimeout(() => { listenforescape = true; }, 0);
|
|
46
|
-
});
|
|
47
|
-
onDestroy(async () => {
|
|
48
|
-
const wasactive = active;
|
|
49
|
-
active = false;
|
|
50
|
-
await tick();
|
|
51
|
-
if (returnfocusto && wasactive) {
|
|
52
|
-
returnfocusto.focus();
|
|
53
|
-
}
|
|
54
|
-
FocusLockStack.pop();
|
|
55
|
-
const prevFocusLock = FocusLockStack.slice(-1)[0];
|
|
56
|
-
if (prevFocusLock)
|
|
57
|
-
prevFocusLock.unpause();
|
|
58
|
-
});
|
|
59
|
-
const setInitialFocus = () => {
|
|
60
|
-
const firstfocus = lockelement ? tabbable(lockelement)[0] : undefined;
|
|
61
|
-
if (firstfocus && firstfocus.focus) {
|
|
62
|
-
firstfocus.focus();
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
const setLastFocus = () => {
|
|
66
|
-
const lastfocus = lockelement ? tabbable(lockelement).slice(-1)[0] : undefined;
|
|
67
|
-
if (lastfocus && lastfocus.focus) {
|
|
68
|
-
lastfocus.focus();
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
const keydown = (e) => {
|
|
72
|
-
if (active && e.key === 'Escape' && escapable) {
|
|
73
|
-
e.preventDefault();
|
|
74
|
-
dispatch('escape');
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
const focusin = (e) => {
|
|
78
|
-
if (active && e.target instanceof HTMLElement && !lockelement?.contains(e.target)) {
|
|
79
|
-
if (e.target === abovelockelement)
|
|
80
|
-
setLastFocus();
|
|
81
|
-
else
|
|
82
|
-
setInitialFocus();
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
const windowclick = (_) => {
|
|
86
|
-
if (listenforescape && active) {
|
|
87
|
-
returnfocusto = undefined;
|
|
88
|
-
dispatch('escape');
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
</script>
|
|
92
|
-
<svelte:window on:click={windowclick} />
|
|
93
|
-
<div class={className} role="alertdialog" aria-modal="true" on:click|stopPropagation on:keydown|stopPropagation={keydown} on:focusin={focusin}>
|
|
94
|
-
<div bind:this={abovelockelement} tabindex="0"></div>
|
|
95
|
-
<div bind:this={lockelement} on:keydown={keydown}>
|
|
96
|
-
{#if hidefocus}<div class="hiddenfocus" use:buttonify on:blur={() => { hidefocus = false }} on:click={() => escapable && dispatch('escape')}><ScreenReaderOnly>{hidefocuslabel}{#if escapable}, click to escape or use escape key at any time{/if}</ScreenReaderOnly></div>{/if}
|
|
97
|
-
<slot></slot>
|
|
98
|
-
</div>
|
|
99
|
-
<div tabindex="0"></div>
|
|
100
|
-
</div>
|
|
101
|
-
|
|
102
|
-
<style>
|
|
103
|
-
.hiddenfocus {
|
|
104
|
-
opacity: 0;
|
|
105
|
-
outline: 0;
|
|
106
|
-
}
|
|
107
|
-
</style>
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
declare const __propDef: {
|
|
3
|
-
props: {
|
|
4
|
-
returnfocusto?: HTMLElement | null | undefined;
|
|
5
|
-
initialfocus?: string | undefined;
|
|
6
|
-
hidefocus?: boolean;
|
|
7
|
-
hidefocuslabel?: string;
|
|
8
|
-
escapable?: boolean;
|
|
9
|
-
class?: string;
|
|
10
|
-
};
|
|
11
|
-
events: {
|
|
12
|
-
click: MouseEvent;
|
|
13
|
-
escape: CustomEvent<any>;
|
|
14
|
-
} & {
|
|
15
|
-
[evt: string]: CustomEvent<any>;
|
|
16
|
-
};
|
|
17
|
-
slots: {
|
|
18
|
-
default: {};
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
export declare type FocusLockProps = typeof __propDef.props;
|
|
22
|
-
export declare type FocusLockEvents = typeof __propDef.events;
|
|
23
|
-
export declare type FocusLockSlots = typeof __propDef.slots;
|
|
24
|
-
export default class FocusLock extends SvelteComponentTyped<FocusLockProps, FocusLockEvents, FocusLockSlots> {
|
|
25
|
-
}
|
|
26
|
-
export {};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
declare const __propDef: {
|
|
3
|
-
props: {
|
|
4
|
-
escapable?: boolean;
|
|
5
|
-
hidefocus?: boolean;
|
|
6
|
-
hidefocuslabel?: string | undefined;
|
|
7
|
-
returnfocusto?: HTMLElement | undefined;
|
|
8
|
-
initialfocus?: string | undefined;
|
|
9
|
-
containerClass?: string;
|
|
10
|
-
opaque?: boolean;
|
|
11
|
-
usePortal?: HTMLElement | undefined;
|
|
12
|
-
};
|
|
13
|
-
events: {
|
|
14
|
-
escape: CustomEvent<any>;
|
|
15
|
-
} & {
|
|
16
|
-
[evt: string]: CustomEvent<any>;
|
|
17
|
-
};
|
|
18
|
-
slots: {
|
|
19
|
-
default: {};
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
export declare type ModalProps = typeof __propDef.props;
|
|
23
|
-
export declare type ModalEvents = typeof __propDef.events;
|
|
24
|
-
export declare type ModalSlots = typeof __propDef.slots;
|
|
25
|
-
export default class Modal extends SvelteComponentTyped<ModalProps, ModalEvents, ModalSlots> {
|
|
26
|
-
}
|
|
27
|
-
export {};
|
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
<script >import { createEventDispatcher, onDestroy, tick } from 'svelte';
|
|
2
|
-
import { randomid } from 'txstate-utils';
|
|
3
|
-
import { Store } from '@txstate-mws/svelte-store';
|
|
4
|
-
import { glue, portal } from '../actions';
|
|
5
|
-
import ScreenReaderOnly from './ScreenReaderOnly.svelte';
|
|
6
|
-
const dispatch = createEventDispatcher();
|
|
7
|
-
export let menushown = false;
|
|
8
|
-
export let menuContainerClass = '';
|
|
9
|
-
export let menuClass = '';
|
|
10
|
-
export let menuItemClass = '';
|
|
11
|
-
export let menuItemHilitedClass = '';
|
|
12
|
-
export let menuItemSelectedClass = '';
|
|
13
|
-
export let items = [];
|
|
14
|
-
export let buttonelement;
|
|
15
|
-
export let align = 'auto';
|
|
16
|
-
export let cover = false;
|
|
17
|
-
export let showSelected = true;
|
|
18
|
-
export let width = undefined;
|
|
19
|
-
export let computedalign = new Store({ valign: 'bottom', halign: 'left' });
|
|
20
|
-
export let usePortal = undefined;
|
|
21
|
-
export let emptyText = undefined;
|
|
22
|
-
export let value = undefined;
|
|
23
|
-
let hilited = undefined;
|
|
24
|
-
let menuelement;
|
|
25
|
-
const itemelements = [];
|
|
26
|
-
const menuid = randomid();
|
|
27
|
-
let firstactive = 0;
|
|
28
|
-
let lastactive = items.length - 1;
|
|
29
|
-
function hiddenItem(item) {
|
|
30
|
-
return !!item && !showSelected && item.value === value;
|
|
31
|
-
}
|
|
32
|
-
async function reactToItems(..._) {
|
|
33
|
-
firstactive = items.findIndex(itm => !itm.disabled && !hiddenItem(itm));
|
|
34
|
-
lastactive = items.length - [...items].reverse().findIndex(itm => !itm.disabled && !hiddenItem(itm)) - 1;
|
|
35
|
-
if (hilited && items[hilited]?.disabled)
|
|
36
|
-
hilited = firstactive;
|
|
37
|
-
}
|
|
38
|
-
$: reactToItems(items, value);
|
|
39
|
-
async function reactToMenuShown(_) {
|
|
40
|
-
if (!buttonelement) {
|
|
41
|
-
hilited = undefined;
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
await tick();
|
|
45
|
-
if (!menushown) {
|
|
46
|
-
hilited = undefined;
|
|
47
|
-
buttonelement.removeAttribute('aria-controls');
|
|
48
|
-
buttonelement.setAttribute('aria-expanded', 'false');
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
buttonelement.setAttribute('aria-controls', menuid);
|
|
52
|
-
buttonelement.setAttribute('aria-expanded', 'true');
|
|
53
|
-
}
|
|
54
|
-
buttonelement.focus();
|
|
55
|
-
}
|
|
56
|
-
$: reactToMenuShown(menushown);
|
|
57
|
-
function move(idx) {
|
|
58
|
-
if (!menushown)
|
|
59
|
-
return;
|
|
60
|
-
if (items[idx]?.disabled)
|
|
61
|
-
return;
|
|
62
|
-
hilited = Math.max(firstactive, Math.min(lastactive, idx));
|
|
63
|
-
itemelements[hilited].scrollIntoView({ block: 'center' });
|
|
64
|
-
buttonelement.setAttribute('aria-activedescendant', `${menuid}-${hilited}`);
|
|
65
|
-
}
|
|
66
|
-
function onkeydown(e) {
|
|
67
|
-
if (e.key === 'ArrowDown') {
|
|
68
|
-
e.preventDefault();
|
|
69
|
-
if (menushown) {
|
|
70
|
-
let i = (hilited ?? firstactive - 1) + 1;
|
|
71
|
-
while (items[i]?.disabled || hiddenItem(items[i]))
|
|
72
|
-
i++;
|
|
73
|
-
move(i);
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
menushown = true;
|
|
77
|
-
move(firstactive);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
else if (e.key === 'ArrowUp') {
|
|
81
|
-
e.preventDefault();
|
|
82
|
-
if (menushown) {
|
|
83
|
-
let i = (hilited ?? lastactive + 1) - 1;
|
|
84
|
-
while (items[i]?.disabled || hiddenItem(items[i]))
|
|
85
|
-
i--;
|
|
86
|
-
move(i);
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
menushown = true;
|
|
90
|
-
move(lastactive);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
else if (e.key === 'Enter') {
|
|
94
|
-
e.preventDefault();
|
|
95
|
-
if (menushown) {
|
|
96
|
-
menushown = false;
|
|
97
|
-
if (typeof hilited !== 'undefined') {
|
|
98
|
-
value = items[hilited]?.value;
|
|
99
|
-
dispatch('change', items[hilited]);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
menushown = true;
|
|
104
|
-
move(firstactive);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
else if (e.key === ' ') {
|
|
108
|
-
// buttonelement might be a text input if this popup is searchable,
|
|
109
|
-
// so we want to allow spaces to be entered without changing the menu situation
|
|
110
|
-
if (menushown) {
|
|
111
|
-
if (typeof hilited !== 'undefined') {
|
|
112
|
-
e.preventDefault();
|
|
113
|
-
menushown = false;
|
|
114
|
-
value = items[hilited]?.value;
|
|
115
|
-
dispatch('change', items[hilited]);
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
if (buttonelement.tagName !== 'INPUT') {
|
|
119
|
-
e.preventDefault();
|
|
120
|
-
menushown = false;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
if (buttonelement.tagName !== 'INPUT') {
|
|
126
|
-
e.preventDefault();
|
|
127
|
-
menushown = true;
|
|
128
|
-
move(firstactive);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
else if (menushown && e.key === 'Escape') {
|
|
133
|
-
e.preventDefault();
|
|
134
|
-
e.stopPropagation();
|
|
135
|
-
menushown = false;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
function onbuttonclick(e) {
|
|
139
|
-
e.preventDefault();
|
|
140
|
-
menushown = !menushown;
|
|
141
|
-
}
|
|
142
|
-
async function onblur(e) {
|
|
143
|
-
// tabindex=-1 on our menu elements means e.relatedTarget will be set
|
|
144
|
-
if (!(e.relatedTarget instanceof HTMLElement && menuelement?.contains(e.relatedTarget)))
|
|
145
|
-
menushown = false;
|
|
146
|
-
}
|
|
147
|
-
function cleanup(element) {
|
|
148
|
-
if (element) {
|
|
149
|
-
element.removeEventListener('click', onbuttonclick);
|
|
150
|
-
element.removeEventListener('keydown', onkeydown);
|
|
151
|
-
element.removeEventListener('blur', onblur);
|
|
152
|
-
element.removeAttribute('aria-disabled');
|
|
153
|
-
element.removeAttribute('aria-haspopup');
|
|
154
|
-
element.removeAttribute('aria-expanded');
|
|
155
|
-
element.removeAttribute('aria-controls');
|
|
156
|
-
element.removeAttribute('aria-activedescendant');
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
onDestroy(() => cleanup(buttonelement));
|
|
160
|
-
// if buttonelement changes we need to handle listeners and aria
|
|
161
|
-
let lastbuttonelement;
|
|
162
|
-
function reactToButtonElement(buttonelement) {
|
|
163
|
-
cleanup(lastbuttonelement);
|
|
164
|
-
lastbuttonelement = buttonelement;
|
|
165
|
-
if (buttonelement) {
|
|
166
|
-
buttonelement.setAttribute('aria-haspopup', 'listbox');
|
|
167
|
-
buttonelement.addEventListener('click', onbuttonclick);
|
|
168
|
-
buttonelement.addEventListener('keydown', onkeydown);
|
|
169
|
-
buttonelement.addEventListener('blur', onblur);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
$: reactToButtonElement(buttonelement);
|
|
173
|
-
const onclick = (item) => (e) => {
|
|
174
|
-
e.stopPropagation();
|
|
175
|
-
e.preventDefault();
|
|
176
|
-
if (item.disabled)
|
|
177
|
-
return;
|
|
178
|
-
menushown = false;
|
|
179
|
-
value = item.value;
|
|
180
|
-
dispatch('change', item);
|
|
181
|
-
};
|
|
182
|
-
$: hasSelected = showSelected && items.some(itm => itm.value === value);
|
|
183
|
-
</script>
|
|
184
|
-
|
|
185
|
-
{#if menushown}
|
|
186
|
-
<div use:portal={usePortal === true ? document.body : (usePortal || null)} use:glue={{ target: buttonelement, align, cover, store: computedalign }} class={menuContainerClass}>
|
|
187
|
-
<ul bind:this={menuelement} id={menuid} role='listbox' style={width ? `width: ${width}` : ''} class={menuClass} class:hasSelected class:defaultmenu={!menuClass && !menuContainerClass} on:keydown={onkeydown}>
|
|
188
|
-
{#each items as item, i (item.value)}
|
|
189
|
-
{#if showSelected || item.value !== value}
|
|
190
|
-
<li
|
|
191
|
-
id={`${menuid}-${i}`}
|
|
192
|
-
bind:this={itemelements[i]}
|
|
193
|
-
class={`${menuItemClass} ${i === hilited ? menuItemHilitedClass || '' : ''} ${value === item.value ? menuItemSelectedClass || '' : ''}`}
|
|
194
|
-
class:disabled={!!item.disabled}
|
|
195
|
-
class:hilited={!menuItemHilitedClass && i === hilited}
|
|
196
|
-
class:selected={showSelected && !menuItemSelectedClass && value === item.value}
|
|
197
|
-
on:click={onclick(item)}
|
|
198
|
-
role="option"
|
|
199
|
-
tabindex=-1
|
|
200
|
-
aria-disabled={item.disabled}
|
|
201
|
-
><slot {item} label={item.label || item.value} hilited={i === hilited} selected={value === item.value}>{item.label || item.value}</slot></li>
|
|
202
|
-
{/if}
|
|
203
|
-
{/each}
|
|
204
|
-
{#if items.length === 0}
|
|
205
|
-
<li role="option" class={`${menuItemClass} disabled`} aria-live="assertive">
|
|
206
|
-
<slot name="noresults">
|
|
207
|
-
{#if !emptyText}
|
|
208
|
-
<span aria-hidden="true">{'¯\\_(ツ)_/¯'}</span><ScreenReaderOnly>{emptyText || 'empty popup menu'}</ScreenReaderOnly>
|
|
209
|
-
{:else}
|
|
210
|
-
{emptyText}
|
|
211
|
-
{/if}
|
|
212
|
-
</slot>
|
|
213
|
-
</li>
|
|
214
|
-
{/if}
|
|
215
|
-
</ul>
|
|
216
|
-
</div>
|
|
217
|
-
{/if}
|
|
218
|
-
|
|
219
|
-
<style>
|
|
220
|
-
div {
|
|
221
|
-
position: absolute !important;
|
|
222
|
-
}
|
|
223
|
-
ul {
|
|
224
|
-
list-style: none;
|
|
225
|
-
}
|
|
226
|
-
ul.defaultmenu {
|
|
227
|
-
margin: 0;
|
|
228
|
-
padding: 0.4em;
|
|
229
|
-
background: white;
|
|
230
|
-
border: 1px solid slategray;
|
|
231
|
-
border-radius: 3px;
|
|
232
|
-
min-width: 10em;
|
|
233
|
-
max-height: 20em;
|
|
234
|
-
overflow-y: auto;
|
|
235
|
-
}
|
|
236
|
-
ul.defaultmenu li.disabled {
|
|
237
|
-
color: rgba(0,0,0,0.6);
|
|
238
|
-
}
|
|
239
|
-
ul.defaultmenu::-webkit-scrollbar {
|
|
240
|
-
appearance: none;
|
|
241
|
-
width: 7px;
|
|
242
|
-
}
|
|
243
|
-
ul.defaultmenu::-webkit-scrollbar-thumb {
|
|
244
|
-
border-radius: 4px;
|
|
245
|
-
background-color: rgba(0,0,0,.5);
|
|
246
|
-
box-shadow: 0 0 1px rgba(255,255,255,.5);
|
|
247
|
-
}
|
|
248
|
-
ul.defaultmenu::-webkit-scrollbar-track {
|
|
249
|
-
background-color: rgba(0,0,0,.1);
|
|
250
|
-
}
|
|
251
|
-
li {
|
|
252
|
-
cursor: pointer;
|
|
253
|
-
}
|
|
254
|
-
li.hilited {
|
|
255
|
-
background: lightblue;
|
|
256
|
-
}
|
|
257
|
-
li.disabled {
|
|
258
|
-
cursor: auto;
|
|
259
|
-
}
|
|
260
|
-
li.selected {
|
|
261
|
-
position: relative;
|
|
262
|
-
}
|
|
263
|
-
ul.hasSelected li {
|
|
264
|
-
padding-left: 1.4em;
|
|
265
|
-
}
|
|
266
|
-
li.selected:after {
|
|
267
|
-
content: ' ';
|
|
268
|
-
position: absolute;
|
|
269
|
-
left: 0.5em;
|
|
270
|
-
top: calc(50% - 0.11em);
|
|
271
|
-
transform: translateY(-50%) rotate(38deg);
|
|
272
|
-
display: block;
|
|
273
|
-
height: 0.786em;
|
|
274
|
-
width: 0.4286em;
|
|
275
|
-
border-bottom: 0.2em solid;
|
|
276
|
-
border-right: 0.2em solid;
|
|
277
|
-
box-sizing: border-box;
|
|
278
|
-
}
|
|
279
|
-
</style>
|