@functionalcms/svelte-components 3.5.15 → 3.5.18
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/css/functional.css +1 -1
- package/css/functional.css.map +1 -1
- package/dist/components/Banner.svelte +1 -1
- package/dist/components/Banner.svelte.d.ts +8 -13
- package/dist/components/Link.svelte.d.ts +45 -34
- package/dist/components/Logo.svelte.d.ts +26 -22
- package/dist/components/Markdown.svelte.d.ts +5 -14
- package/dist/components/Spacer.svelte.d.ts +6 -14
- package/dist/components/Well.svelte.d.ts +11 -13
- package/dist/components/agnostic/Button/Button.svelte +333 -332
- package/dist/components/agnostic/Button/Button.svelte.d.ts +50 -39
- package/dist/components/agnostic/Button/ButtonGroup.svelte.d.ts +30 -19
- package/dist/components/agnostic/Close/Close.svelte.d.ts +22 -18
- package/dist/components/agnostic/Disclose/Disclose.svelte.d.ts +10 -13
- package/dist/components/agnostic/Divider/Divider.svelte.d.ts +21 -17
- package/dist/components/agnostic/Loader/Loader.svelte.d.ts +20 -16
- package/dist/components/blog/BlogDescription.svelte.d.ts +21 -17
- package/dist/components/blog/BlogTitle.svelte.d.ts +21 -17
- package/dist/components/form/DateTimePicker.svelte.d.ts +16 -12
- package/dist/components/form/Input.svelte +1 -1
- package/dist/components/form/Input.svelte.d.ts +46 -20
- package/dist/components/form/InputAddonItem.svelte +34 -34
- package/dist/components/form/InputAddonItem.svelte.d.ts +27 -27
- package/dist/components/form/Select.svelte.d.ts +32 -28
- package/dist/components/{agnostic/Switch → form}/Switch.svelte +1 -1
- package/dist/components/form/Switch.svelte.d.ts +30 -0
- package/dist/components/layouts/DefaultLayout.svelte.d.ts +11 -13
- package/dist/components/layouts/Meta.svelte.d.ts +10 -13
- package/dist/components/layouts/SimpleFooter.svelte.d.ts +7 -13
- package/dist/components/layouts/Tracker.svelte.d.ts +5 -13
- package/dist/components/layouts/TwoColumnsLayout.svelte.d.ts +9 -13
- package/dist/components/menu/CollapsibleMenu.svelte.d.ts +27 -23
- package/dist/components/menu/DynamicMenu.svelte.d.ts +27 -23
- package/dist/components/menu/HamburgerMenu.svelte +3 -3
- package/dist/components/menu/HamburgerMenu.svelte.d.ts +25 -21
- package/dist/components/menu/Menu.svelte.d.ts +31 -27
- package/dist/components/menu/MenuItem.svelte.d.ts +37 -25
- package/dist/components/menu/NavigationItems.svelte +9 -4
- package/dist/components/menu/NavigationItems.svelte.d.ts +27 -23
- package/dist/components/presentation/Card.svelte.d.ts +17 -13
- package/dist/components/presentation/Carousel/carousel-content.svelte +35 -0
- package/dist/components/presentation/Carousel/carousel-content.svelte.d.ts +28 -0
- package/dist/components/presentation/Carousel/carousel-item.svelte +25 -0
- package/dist/components/presentation/Carousel/carousel-item.svelte.d.ts +28 -0
- package/dist/components/presentation/Carousel/carousel-next.svelte +39 -0
- package/dist/components/presentation/Carousel/carousel-next.svelte.d.ts +18 -0
- package/dist/components/presentation/Carousel/carousel-previous.svelte +40 -0
- package/dist/components/presentation/Carousel/carousel-previous.svelte.d.ts +18 -0
- package/dist/components/presentation/Carousel/carousel.svelte +99 -0
- package/dist/components/presentation/Carousel/carousel.svelte.d.ts +31 -0
- package/dist/components/presentation/Carousel/context.d.ts +32 -0
- package/dist/components/presentation/Carousel/context.js +12 -0
- package/dist/components/presentation/Carousel.svelte +4 -0
- package/dist/components/presentation/Carousel.svelte.d.ts +18 -0
- package/dist/components/presentation/Carusele.d.ts +1 -1
- package/dist/components/presentation/Carusele.js +1 -1
- package/dist/components/presentation/Gallery.svelte.d.ts +14 -13
- package/dist/components/presentation/ImageCompare.svelte +15 -12
- package/dist/components/presentation/ImageCompare.svelte.d.ts +24 -38
- package/dist/index.d.ts +5 -4
- package/dist/index.js +12 -7
- package/package.json +24 -24
- package/dist/components/agnostic/Alert/Alert.svelte +0 -317
- package/dist/components/agnostic/Alert/Alert.svelte.d.ts +0 -29
- package/dist/components/agnostic/Avatar/Avatar.svelte +0 -127
- package/dist/components/agnostic/Avatar/Avatar.svelte.d.ts +0 -24
- package/dist/components/agnostic/Avatar/AvatarGroup.svelte +0 -106
- package/dist/components/agnostic/Avatar/AvatarGroup.svelte.d.ts +0 -27
- package/dist/components/agnostic/Breadcrumb/Breadcrumb.svelte +0 -65
- package/dist/components/agnostic/Breadcrumb/Breadcrumb.svelte.d.ts +0 -18
- package/dist/components/agnostic/Breadcrumb/api.d.ts +0 -4
- package/dist/components/agnostic/Breadcrumb/api.js +0 -1
- package/dist/components/agnostic/ChoiceInput/ChoiceInput.svelte +0 -365
- package/dist/components/agnostic/ChoiceInput/ChoiceInput.svelte.d.ts +0 -35
- package/dist/components/agnostic/ChoiceInput/api.d.ts +0 -7
- package/dist/components/agnostic/ChoiceInput/api.js +0 -1
- package/dist/components/agnostic/Dialog/Dialog.svelte +0 -278
- package/dist/components/agnostic/Dialog/Dialog.svelte.d.ts +0 -37
- package/dist/components/agnostic/Dialog/SvelteA11yDialog.svelte +0 -128
- package/dist/components/agnostic/Dialog/SvelteA11yDialog.svelte.d.ts +0 -28
- package/dist/components/agnostic/Dialog/a11y-dialog.d.ts +0 -56
- package/dist/components/agnostic/Dialog/a11y-dialog.js +0 -216
- package/dist/components/agnostic/Dialog/dom-utils.d.ts +0 -26
- package/dist/components/agnostic/Dialog/dom-utils.js +0 -206
- package/dist/components/agnostic/Drawer/Drawer.svelte +0 -34
- package/dist/components/agnostic/Drawer/Drawer.svelte.d.ts +0 -26
- package/dist/components/agnostic/Drawer/api.d.ts +0 -1
- package/dist/components/agnostic/Drawer/api.js +0 -1
- package/dist/components/agnostic/EmptyState/EmptyState.svelte +0 -49
- package/dist/components/agnostic/EmptyState/EmptyState.svelte.d.ts +0 -21
- package/dist/components/agnostic/Header/Header.svelte +0 -111
- package/dist/components/agnostic/Header/Header.svelte.d.ts +0 -24
- package/dist/components/agnostic/Header/HeaderNav.svelte +0 -29
- package/dist/components/agnostic/Header/HeaderNav.svelte.d.ts +0 -18
- package/dist/components/agnostic/Header/HeaderNavItem.svelte +0 -31
- package/dist/components/agnostic/Header/HeaderNavItem.svelte.d.ts +0 -18
- package/dist/components/agnostic/Icon/Icon.svelte +0 -188
- package/dist/components/agnostic/Icon/Icon.svelte.d.ts +0 -21
- package/dist/components/agnostic/Icon/api.d.ts +0 -2
- package/dist/components/agnostic/Icon/api.js +0 -1
- package/dist/components/agnostic/Progress/Progress.svelte +0 -51
- package/dist/components/agnostic/Progress/Progress.svelte.d.ts +0 -18
- package/dist/components/agnostic/Spinner/Spinner.svelte +0 -108
- package/dist/components/agnostic/Spinner/Spinner.svelte.d.ts +0 -17
- package/dist/components/agnostic/Switch/Switch.svelte.d.ts +0 -43
- package/dist/components/agnostic/Table/Table.svelte +0 -521
- package/dist/components/agnostic/Table/Table.svelte.d.ts +0 -34
- package/dist/components/agnostic/Table/TableCustomRenderComponent.svelte +0 -13
- package/dist/components/agnostic/Table/TableCustomRenderComponent.svelte.d.ts +0 -23
- package/dist/components/agnostic/Tabs/TabButtonCustom.svelte +0 -77
- package/dist/components/agnostic/Tabs/TabButtonCustom.svelte.d.ts +0 -33
- package/dist/components/agnostic/Tabs/Tabs.svelte +0 -399
- package/dist/components/agnostic/Tabs/Tabs.svelte.d.ts +0 -32
- package/dist/components/agnostic/Tabs/api.d.ts +0 -10
- package/dist/components/agnostic/Tabs/api.js +0 -1
- package/dist/components/agnostic/Tag/Tag.svelte +0 -78
- package/dist/components/agnostic/Tag/Tag.svelte.d.ts +0 -21
- package/dist/components/agnostic/Tag/TagSlots.svelte +0 -52
- package/dist/components/agnostic/Tag/TagSlots.svelte.d.ts +0 -14
- package/dist/components/agnostic/Toasts/Toasts.svelte +0 -56
- package/dist/components/agnostic/Toasts/Toasts.svelte.d.ts +0 -20
- package/dist/components/agnostic/Tooltip/Tooltip.svelte +0 -120
- package/dist/components/agnostic/Tooltip/Tooltip.svelte.d.ts +0 -21
- package/dist/components/agnostic/Tooltip/TooltipSlots.svelte +0 -82
- package/dist/components/agnostic/Tooltip/TooltipSlots.svelte.d.ts +0 -14
- package/dist/components/agnostic/Tooltip/api.d.ts +0 -1
- package/dist/components/agnostic/Tooltip/api.js +0 -1
- package/dist/components/presentation/Carusel.svelte +0 -109
- package/dist/components/presentation/Carusel.svelte.d.ts +0 -56
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import { closest, focus, getActiveEl, trapTabKey } from './dom-utils.js';
|
|
2
|
-
const SCOPE = 'data-a11y-dialog';
|
|
3
|
-
export default class A11yDialog {
|
|
4
|
-
$el;
|
|
5
|
-
id;
|
|
6
|
-
previouslyFocused;
|
|
7
|
-
shown;
|
|
8
|
-
constructor(element) {
|
|
9
|
-
this.$el = element;
|
|
10
|
-
this.id = this.$el.getAttribute(SCOPE) || this.$el.id;
|
|
11
|
-
this.previouslyFocused = null;
|
|
12
|
-
this.shown = false;
|
|
13
|
-
this.maintainFocus = this.maintainFocus.bind(this);
|
|
14
|
-
this.bindKeypress = this.bindKeypress.bind(this);
|
|
15
|
-
this.handleTriggerClicks = this.handleTriggerClicks.bind(this);
|
|
16
|
-
this.show = this.show.bind(this);
|
|
17
|
-
this.hide = this.hide.bind(this);
|
|
18
|
-
this.$el.setAttribute('aria-hidden', 'true');
|
|
19
|
-
this.$el.setAttribute('aria-modal', 'true');
|
|
20
|
-
this.$el.setAttribute('tabindex', '-1');
|
|
21
|
-
if (!this.$el.hasAttribute('role')) {
|
|
22
|
-
this.$el.setAttribute('role', 'dialog');
|
|
23
|
-
}
|
|
24
|
-
document.addEventListener('click', this.handleTriggerClicks, true);
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Destroy the current instance (after making sure the dialog has been hidden)
|
|
28
|
-
* and remove all associated listeners from dialog openers and closers
|
|
29
|
-
*/
|
|
30
|
-
destroy() {
|
|
31
|
-
// Dispatch a `destroy` event
|
|
32
|
-
const destroyEvent = this.fire('destroy');
|
|
33
|
-
// If the event was prevented, do not continue with the normal behavior
|
|
34
|
-
if (destroyEvent.defaultPrevented)
|
|
35
|
-
return this;
|
|
36
|
-
// Hide the dialog to avoid destroying an open instance
|
|
37
|
-
this.hide();
|
|
38
|
-
// Remove the click event delegates for our openers and closers
|
|
39
|
-
document.removeEventListener('click', this.handleTriggerClicks, true);
|
|
40
|
-
// Clone and replace the dialog element to prevent memory leaks caused by
|
|
41
|
-
// event listeners that the author might not have cleaned up.
|
|
42
|
-
this.$el.replaceWith(this.$el.cloneNode(true));
|
|
43
|
-
return this;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Show the dialog element, trap the current focus within it, listen for some
|
|
47
|
-
* specific key presses and fire all registered callbacks for `show` event
|
|
48
|
-
*/
|
|
49
|
-
show(event) {
|
|
50
|
-
// If the dialog is already open, abort
|
|
51
|
-
if (this.shown)
|
|
52
|
-
return this;
|
|
53
|
-
// Dispatch a `show` event
|
|
54
|
-
const showEvent = this.fire('show', event);
|
|
55
|
-
// If the event was prevented, do not continue with the normal behavior
|
|
56
|
-
if (showEvent.defaultPrevented)
|
|
57
|
-
return this;
|
|
58
|
-
// Keep a reference to the currently focused element to be able to restore
|
|
59
|
-
// it later
|
|
60
|
-
this.shown = true;
|
|
61
|
-
this.$el.removeAttribute('aria-hidden');
|
|
62
|
-
this.previouslyFocused = getActiveEl();
|
|
63
|
-
// Due to a long lasting bug in Safari, clicking an interactive element
|
|
64
|
-
// (like a <button>) does *not* move the focus to that element, which means
|
|
65
|
-
// `document.activeElement` is whatever element is currently focused (like
|
|
66
|
-
// an <input>), or the <body> element otherwise. We can work around that
|
|
67
|
-
// problem by checking whether the focused element is the <body>, and if it,
|
|
68
|
-
// store the click event target.
|
|
69
|
-
// See: https://bugs.webkit.org/show_bug.cgi?id=22261
|
|
70
|
-
if (this.previouslyFocused?.tagName === 'BODY' && event?.target) {
|
|
71
|
-
this.previouslyFocused = event.target;
|
|
72
|
-
}
|
|
73
|
-
// Set the focus to the dialog element
|
|
74
|
-
// See: https://github.com/KittyGiraudel/a11y-dialog/pull/583
|
|
75
|
-
if (event?.type === 'focus') {
|
|
76
|
-
this.maintainFocus(event);
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
focus(this.$el);
|
|
80
|
-
}
|
|
81
|
-
// Bind a focus event listener to the body element to make sure the focus
|
|
82
|
-
// stays trapped inside the dialog while open, and start listening for some
|
|
83
|
-
// specific key presses (TAB and ESC)
|
|
84
|
-
document.body.addEventListener('focus', this.maintainFocus, true);
|
|
85
|
-
this.$el.addEventListener('keydown', this.bindKeypress, true);
|
|
86
|
-
return this;
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Hide the dialog element, restore the focus to the previously active
|
|
90
|
-
* element, stop listening for some specific key presses and fire all
|
|
91
|
-
* registered callbacks for `hide` event
|
|
92
|
-
*/
|
|
93
|
-
hide(event) {
|
|
94
|
-
// If the dialog is already closed, abort
|
|
95
|
-
if (!this.shown)
|
|
96
|
-
return this;
|
|
97
|
-
// Dispatch a `hide` event
|
|
98
|
-
const hideEvent = this.fire('hide', event);
|
|
99
|
-
// If the event was prevented, do not continue with the normal behavior
|
|
100
|
-
if (hideEvent.defaultPrevented)
|
|
101
|
-
return this;
|
|
102
|
-
this.shown = false;
|
|
103
|
-
this.$el.setAttribute('aria-hidden', 'true');
|
|
104
|
-
// Ensure the previously focused element (if any) has a `focus` method
|
|
105
|
-
// before attempting to call it to account for SVG elements
|
|
106
|
-
// See: https://github.com/KittyGiraudel/a11y-dialog/issues/108
|
|
107
|
-
this.previouslyFocused?.focus?.();
|
|
108
|
-
// Remove the focus event listener to the body element and stop listening
|
|
109
|
-
// for specific key presses
|
|
110
|
-
document.body.removeEventListener('focus', this.maintainFocus, true);
|
|
111
|
-
this.$el.removeEventListener('keydown', this.bindKeypress, true);
|
|
112
|
-
return this;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Register a new callback for the given event type
|
|
116
|
-
*/
|
|
117
|
-
on(type, handler, options) {
|
|
118
|
-
this.$el.addEventListener(type, handler, options);
|
|
119
|
-
return this;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Unregister an existing callback for the given event type
|
|
123
|
-
*/
|
|
124
|
-
off(type, handler, options) {
|
|
125
|
-
this.$el.removeEventListener(type, handler, options);
|
|
126
|
-
return this;
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Dispatch and return a custom event from the DOM element associated with
|
|
130
|
-
* this dialog; this allows authors to listen for and respond to the events
|
|
131
|
-
* in their own code
|
|
132
|
-
*/
|
|
133
|
-
fire(type, event) {
|
|
134
|
-
const customEvent = new CustomEvent(type, {
|
|
135
|
-
detail: event,
|
|
136
|
-
cancelable: true,
|
|
137
|
-
});
|
|
138
|
-
this.$el.dispatchEvent(customEvent);
|
|
139
|
-
return customEvent;
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Add a delegated event listener for when elememts that open or close the
|
|
143
|
-
* dialog are clicked, and call `show` or `hide`, respectively
|
|
144
|
-
*/
|
|
145
|
-
handleTriggerClicks(event) {
|
|
146
|
-
// We need to retrieve the click target while accounting for Shadow DOM.
|
|
147
|
-
// When within a web component, `event.target` is the shadow root (e.g.
|
|
148
|
-
// `<my-dialog>`), so we need to use `event.composedPath()` to get the click
|
|
149
|
-
// target
|
|
150
|
-
// See: https://github.com/KittyGiraudel/a11y-dialog/issues/582
|
|
151
|
-
const target = event.composedPath()[0];
|
|
152
|
-
const opener = closest(`[${SCOPE}-show="${this.id}"]`, target);
|
|
153
|
-
const explicitCloser = closest(`[${SCOPE}-hide="${this.id}"]`, target);
|
|
154
|
-
const implicitCloser = closest(`[${SCOPE}-hide]`, target) &&
|
|
155
|
-
closest('[aria-modal="true"]', target) === this.$el;
|
|
156
|
-
// We use `closest(..)` (instead of `matches(..)`) so that clicking an
|
|
157
|
-
// element nested within a dialog opener does cause the dialog to open, and
|
|
158
|
-
// we use our custom `closest(..)` function so that it can cross shadow
|
|
159
|
-
// boundaries
|
|
160
|
-
// See: https://github.com/KittyGiraudel/a11y-dialog/issues/712
|
|
161
|
-
if (opener)
|
|
162
|
-
this.show(event);
|
|
163
|
-
if (explicitCloser || implicitCloser)
|
|
164
|
-
this.hide(event);
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Private event handler used when listening to some specific key presses
|
|
168
|
-
* (namely ESC and TAB)
|
|
169
|
-
*/
|
|
170
|
-
bindKeypress(event) {
|
|
171
|
-
// This is an escape hatch in case there are nested open dialogs, so that
|
|
172
|
-
// only the top most dialog gets interacted with (`closest` is basically
|
|
173
|
-
// `Element.prototype.closest()` accounting for Shadow DOM subtrees)
|
|
174
|
-
if (closest('[aria-modal="true"]', getActiveEl()) !== this.$el) {
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
let hasOpenPopover = false;
|
|
178
|
-
try {
|
|
179
|
-
hasOpenPopover = !!this.$el.querySelector('[popover]:not([popover="manual"]):popover-open');
|
|
180
|
-
}
|
|
181
|
-
catch {
|
|
182
|
-
// Run that DOM query in a try/catch because not all browsers support the
|
|
183
|
-
// `:popover-open` selector, which would cause the whole expression to
|
|
184
|
-
// fail
|
|
185
|
-
// See: https://caniuse.com/mdn-css_selectors_popover-open
|
|
186
|
-
// See: https://github.com/KittyGiraudel/a11y-dialog/pull/578#discussion_r1343215149
|
|
187
|
-
}
|
|
188
|
-
// If the dialog is shown and the ESC key is pressed, prevent any further
|
|
189
|
-
// effects from the ESC key and hide the dialog, unless:
|
|
190
|
-
// - its role is `alertdialog`, which means it should be modal
|
|
191
|
-
// - or it contains an open popover, in which case ESC should close it
|
|
192
|
-
if (event.key === 'Escape' &&
|
|
193
|
-
this.$el.getAttribute('role') !== 'alertdialog' &&
|
|
194
|
-
!hasOpenPopover) {
|
|
195
|
-
event.preventDefault();
|
|
196
|
-
this.hide(event);
|
|
197
|
-
}
|
|
198
|
-
// If the dialog is shown and the TAB key is pressed, make sure the focus
|
|
199
|
-
// stays trapped within the dialog element
|
|
200
|
-
if (event.key === 'Tab') {
|
|
201
|
-
trapTabKey(this.$el, event);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* If the dialog is shown and the focus is not within a dialog element (either
|
|
206
|
-
* this one or another one in case of nested dialogs) or attribute, move it
|
|
207
|
-
* back to the dialog container
|
|
208
|
-
* See: https://github.com/KittyGiraudel/a11y-dialog/issues/177
|
|
209
|
-
*/
|
|
210
|
-
maintainFocus(event) {
|
|
211
|
-
const target = event.target;
|
|
212
|
-
if (!target.closest(`[aria-modal="true"], [${SCOPE}-ignore-focus-trap]`)) {
|
|
213
|
-
focus(this.$el);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Set the focus to the first element with `autofocus` with the element or the
|
|
3
|
-
* element itself.
|
|
4
|
-
*/
|
|
5
|
-
export declare function focus(el: HTMLElement): void;
|
|
6
|
-
/**
|
|
7
|
-
* Get the first and last focusable elements within a given element.
|
|
8
|
-
*/
|
|
9
|
-
export declare function getFocusableEdges(el: HTMLElement): readonly [HTMLElement | null, HTMLElement | null];
|
|
10
|
-
/**
|
|
11
|
-
* Get the active element, accounting for Shadow DOM subtrees.
|
|
12
|
-
* @author Cory LaViska
|
|
13
|
-
* @see: https://www.abeautifulsite.net/posts/finding-the-active-element-in-a-shadow-root/
|
|
14
|
-
*/
|
|
15
|
-
export declare function getActiveEl(root?: Document | ShadowRoot): Element | null;
|
|
16
|
-
/**
|
|
17
|
-
* Trap the focus inside the given element
|
|
18
|
-
*/
|
|
19
|
-
export declare function trapTabKey(el: HTMLElement, event: KeyboardEvent): void;
|
|
20
|
-
/**
|
|
21
|
-
* Find the closest element to the given element matching the given selector,
|
|
22
|
-
* accounting for Shadow DOM subtrees.
|
|
23
|
-
* @author Louis St-Amour
|
|
24
|
-
* @see: https://stackoverflow.com/a/56105394
|
|
25
|
-
*/
|
|
26
|
-
export declare function closest(selector: string, base: Element | null): Element | null;
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import focusableSelectors from 'focusable-selectors';
|
|
2
|
-
/**
|
|
3
|
-
* Set the focus to the first element with `autofocus` with the element or the
|
|
4
|
-
* element itself.
|
|
5
|
-
*/
|
|
6
|
-
export function focus(el) {
|
|
7
|
-
;
|
|
8
|
-
(el.querySelector('[autofocus]') || el).focus();
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Get the first and last focusable elements within a given element.
|
|
12
|
-
*/
|
|
13
|
-
export function getFocusableEdges(el) {
|
|
14
|
-
// Check for a focusable element within the subtree of the given element.
|
|
15
|
-
const firstEl = findFocusableEl(el, true);
|
|
16
|
-
// Only if we find the first element do we need to look for the last one. If
|
|
17
|
-
// there’s no last element, we set `lastEl` as a reference to `firstEl` so
|
|
18
|
-
// that the returned array is still always of length 2.
|
|
19
|
-
const lastEl = firstEl ? findFocusableEl(el, false) || firstEl : null;
|
|
20
|
-
return [firstEl, lastEl];
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Find the first focusable element inside the given element if `forward` is
|
|
24
|
-
* truthy or the last focusable element otherwise.
|
|
25
|
-
*/
|
|
26
|
-
function findFocusableEl(el, forward) {
|
|
27
|
-
// If we’re walking forward, check if this element is focusable, and return it
|
|
28
|
-
// immediately if it is.
|
|
29
|
-
if (forward && isFocusable(el))
|
|
30
|
-
return el;
|
|
31
|
-
// We should only search the subtree of this element if it can have focusable
|
|
32
|
-
// children.
|
|
33
|
-
if (canHaveFocusableChildren(el)) {
|
|
34
|
-
// Start walking the DOM tree, looking for focusable elements.
|
|
35
|
-
// Case 1: If this element has a shadow root, search it recursively.
|
|
36
|
-
if (el.shadowRoot) {
|
|
37
|
-
// Descend into this subtree.
|
|
38
|
-
let next = getNextChildEl(el.shadowRoot, forward);
|
|
39
|
-
// Traverse the siblings, searching the subtree of each one for focusable
|
|
40
|
-
// elements.
|
|
41
|
-
while (next) {
|
|
42
|
-
const focusableEl = findFocusableEl(next, forward);
|
|
43
|
-
if (focusableEl)
|
|
44
|
-
return focusableEl;
|
|
45
|
-
next = getNextSiblingEl(next, forward);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
// Case 2: If this element is a slot for a Custom Element, search its
|
|
49
|
-
// assigned elements recursively.
|
|
50
|
-
else if (el.localName === 'slot') {
|
|
51
|
-
const assignedElements = el.assignedElements({
|
|
52
|
-
flatten: true,
|
|
53
|
-
});
|
|
54
|
-
if (!forward)
|
|
55
|
-
assignedElements.reverse();
|
|
56
|
-
for (const assignedElement of assignedElements) {
|
|
57
|
-
const focusableEl = findFocusableEl(assignedElement, forward);
|
|
58
|
-
if (focusableEl)
|
|
59
|
-
return focusableEl;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
// Case 3: this is a regular Light DOM element. Search its subtree.
|
|
63
|
-
else {
|
|
64
|
-
// Descend into this subtree.
|
|
65
|
-
let next = getNextChildEl(el, forward);
|
|
66
|
-
// Traverse siblings, searching the subtree of each one
|
|
67
|
-
// for focusable elements.
|
|
68
|
-
while (next) {
|
|
69
|
-
const focusableEl = findFocusableEl(next, forward);
|
|
70
|
-
if (focusableEl)
|
|
71
|
-
return focusableEl;
|
|
72
|
-
next = getNextSiblingEl(next, forward);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
// If we’re walking backward, we want to check the element’s entire subtree
|
|
77
|
-
// before checking the element itself. If this element is focusable, return
|
|
78
|
-
// it.
|
|
79
|
-
if (!forward && isFocusable(el))
|
|
80
|
-
return el;
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
function getNextChildEl(el, forward) {
|
|
84
|
-
return forward ? el.firstElementChild : el.lastElementChild;
|
|
85
|
-
}
|
|
86
|
-
function getNextSiblingEl(el, forward) {
|
|
87
|
-
return forward ? el.nextElementSibling : el.previousElementSibling;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Determine if an element is hidden from the user.
|
|
91
|
-
*/
|
|
92
|
-
const isHidden = (el) => {
|
|
93
|
-
// Browsers hide all non-<summary> descendants of closed <details> elements
|
|
94
|
-
// from user interaction, but those non-<summary> elements may still match our
|
|
95
|
-
// focusable-selectors and may still have dimensions, so we need a special
|
|
96
|
-
// case to ignore them.
|
|
97
|
-
if (el.matches('details:not([open]) *') &&
|
|
98
|
-
!el.matches('details>summary:first-of-type'))
|
|
99
|
-
return true;
|
|
100
|
-
// If this element has no painted dimensions, it's hidden.
|
|
101
|
-
return !(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
|
|
102
|
-
};
|
|
103
|
-
/**
|
|
104
|
-
* Determine if an element is focusable and has user-visible painted dimensions.
|
|
105
|
-
*/
|
|
106
|
-
const isFocusable = (el) => {
|
|
107
|
-
// A shadow host that delegates focus will never directly receive focus,
|
|
108
|
-
// even with `tabindex=0`. Consider our <fancy-button> custom element, which
|
|
109
|
-
// delegates focus to its shadow button:
|
|
110
|
-
//
|
|
111
|
-
// <fancy-button tabindex="0">
|
|
112
|
-
// #shadow-root
|
|
113
|
-
// <button><slot></slot></button>
|
|
114
|
-
// </fancy-button>
|
|
115
|
-
//
|
|
116
|
-
// The browser acts as as if there is only one focusable element – the shadow
|
|
117
|
-
// button. Our library should behave the same way.
|
|
118
|
-
if (el.shadowRoot?.delegatesFocus)
|
|
119
|
-
return false;
|
|
120
|
-
return el.matches(focusableSelectors.join(',')) && !isHidden(el);
|
|
121
|
-
};
|
|
122
|
-
/**
|
|
123
|
-
* Determine if an element can have focusable children. Useful for bailing out
|
|
124
|
-
* early when walking the DOM tree.
|
|
125
|
-
* @example
|
|
126
|
-
* This div is inert, so none of its children can be focused, even though they
|
|
127
|
-
* meet our criteria for what is focusable. Once we check the div, we can skip
|
|
128
|
-
* the rest of the subtree.
|
|
129
|
-
* ```html
|
|
130
|
-
* <div inert>
|
|
131
|
-
* <button>Button</button>
|
|
132
|
-
* <a href="#">Link</a>
|
|
133
|
-
* </div>
|
|
134
|
-
* ```
|
|
135
|
-
*/
|
|
136
|
-
function canHaveFocusableChildren(el) {
|
|
137
|
-
// The browser will never send focus into a Shadow DOM if the host element
|
|
138
|
-
// has a negative tabindex. This applies to both slotted Light DOM Shadow DOM
|
|
139
|
-
// children
|
|
140
|
-
if (el.shadowRoot && el.getAttribute('tabindex') === '-1')
|
|
141
|
-
return false;
|
|
142
|
-
// Elemments matching this selector are either hidden entirely from the user,
|
|
143
|
-
// or are visible but unavailable for interaction. Their descentants can never
|
|
144
|
-
// receive focus.
|
|
145
|
-
return !el.matches(':disabled,[hidden],[inert]');
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Get the active element, accounting for Shadow DOM subtrees.
|
|
149
|
-
* @author Cory LaViska
|
|
150
|
-
* @see: https://www.abeautifulsite.net/posts/finding-the-active-element-in-a-shadow-root/
|
|
151
|
-
*/
|
|
152
|
-
export function getActiveEl(root = document) {
|
|
153
|
-
const activeEl = root.activeElement;
|
|
154
|
-
if (!activeEl)
|
|
155
|
-
return null;
|
|
156
|
-
// If there’s a shadow root, recursively find the active element within it.
|
|
157
|
-
// If the recursive call returns null, return the active element
|
|
158
|
-
// of the top-level Document.
|
|
159
|
-
if (activeEl.shadowRoot)
|
|
160
|
-
return getActiveEl(activeEl.shadowRoot) || document.activeElement;
|
|
161
|
-
// If not, we can just return the active element
|
|
162
|
-
return activeEl;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Trap the focus inside the given element
|
|
166
|
-
*/
|
|
167
|
-
export function trapTabKey(el, event) {
|
|
168
|
-
const [firstFocusableEl, lastFocusableEl] = getFocusableEdges(el);
|
|
169
|
-
// If there are no focusable children in the dialog, prevent the user from
|
|
170
|
-
// tabbing out of it
|
|
171
|
-
if (!firstFocusableEl)
|
|
172
|
-
return event.preventDefault();
|
|
173
|
-
const activeEl = getActiveEl();
|
|
174
|
-
// If the SHIFT key is pressed while tabbing (moving backwards) and the
|
|
175
|
-
// currently focused item is the first one, move the focus to the last
|
|
176
|
-
// focusable item from the dialog element
|
|
177
|
-
if (event.shiftKey && activeEl === firstFocusableEl) {
|
|
178
|
-
// @ts-ignore: we know that `lastFocusableEl` is not null here
|
|
179
|
-
lastFocusableEl.focus();
|
|
180
|
-
event.preventDefault();
|
|
181
|
-
}
|
|
182
|
-
// If the SHIFT key is not pressed (moving forwards) and the currently focused
|
|
183
|
-
// item is the last one, move the focus to the first focusable item from the
|
|
184
|
-
// dialog element
|
|
185
|
-
else if (!event.shiftKey && activeEl === lastFocusableEl) {
|
|
186
|
-
firstFocusableEl.focus();
|
|
187
|
-
event.preventDefault();
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Find the closest element to the given element matching the given selector,
|
|
192
|
-
* accounting for Shadow DOM subtrees.
|
|
193
|
-
* @author Louis St-Amour
|
|
194
|
-
* @see: https://stackoverflow.com/a/56105394
|
|
195
|
-
*/
|
|
196
|
-
export function closest(selector, base) {
|
|
197
|
-
function from(el) {
|
|
198
|
-
if (!el || el === document || el === window)
|
|
199
|
-
return null;
|
|
200
|
-
if (el.assignedSlot)
|
|
201
|
-
el = el.assignedSlot;
|
|
202
|
-
return (el.closest(selector) ||
|
|
203
|
-
from(el.getRootNode().host));
|
|
204
|
-
}
|
|
205
|
-
return from(base);
|
|
206
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { DrawerRoles } from './api';
|
|
3
|
-
import Dialog from "../Dialog/Dialog.svelte";
|
|
4
|
-
import { createEventDispatcher } from "svelte";
|
|
5
|
-
const dispatch = createEventDispatcher();
|
|
6
|
-
|
|
7
|
-
let drawerInstance;
|
|
8
|
-
const assignDrawerRef = (ev) => {
|
|
9
|
-
drawerInstance = ev.detail.instance;
|
|
10
|
-
dispatch("instance", {
|
|
11
|
-
instance: drawerInstance,
|
|
12
|
-
});
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export let id;
|
|
16
|
-
export let drawerRoot;
|
|
17
|
-
export let placement;
|
|
18
|
-
export let title;
|
|
19
|
-
export let role: DrawerRoles = "dialog";
|
|
20
|
-
export let isAnimationFadeIn = true;
|
|
21
|
-
</script>
|
|
22
|
-
<Dialog
|
|
23
|
-
id={id}
|
|
24
|
-
dialogRoot={drawerRoot}
|
|
25
|
-
drawerPlacement={placement}
|
|
26
|
-
titleId="{`${title.replaceAll(' ', '-').toLowerCase()}-id`}"
|
|
27
|
-
role={role}
|
|
28
|
-
title={title}
|
|
29
|
-
on:instance={assignDrawerRef}
|
|
30
|
-
isAnimationFadeIn={isAnimationFadeIn}
|
|
31
|
-
closeButtonLabel="Close drawer"
|
|
32
|
-
>
|
|
33
|
-
<slot />
|
|
34
|
-
</Dialog>
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
import type { DrawerRoles } from './api';
|
|
3
|
-
declare const __propDef: {
|
|
4
|
-
props: {
|
|
5
|
-
id: any;
|
|
6
|
-
drawerRoot: any;
|
|
7
|
-
placement: any;
|
|
8
|
-
title: any;
|
|
9
|
-
role?: DrawerRoles;
|
|
10
|
-
isAnimationFadeIn?: boolean;
|
|
11
|
-
};
|
|
12
|
-
events: {
|
|
13
|
-
instance: CustomEvent<any>;
|
|
14
|
-
} & {
|
|
15
|
-
[evt: string]: CustomEvent<any>;
|
|
16
|
-
};
|
|
17
|
-
slots: {
|
|
18
|
-
default: {};
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
export type DrawerProps = typeof __propDef.props;
|
|
22
|
-
export type DrawerEvents = typeof __propDef.events;
|
|
23
|
-
export type DrawerSlots = typeof __propDef.slots;
|
|
24
|
-
export default class Drawer extends SvelteComponentTyped<DrawerProps, DrawerEvents, DrawerSlots> {
|
|
25
|
-
}
|
|
26
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export type DrawerRoles = 'alertdialog' | 'dialog';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
<style>
|
|
2
|
-
.empty-base,
|
|
3
|
-
.empty {
|
|
4
|
-
display: flex;
|
|
5
|
-
flex-flow: column wrap;
|
|
6
|
-
align-items: center;
|
|
7
|
-
text-align: center;
|
|
8
|
-
width: 100%;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
.empty {
|
|
12
|
-
padding: calc(2 * var(--functional-side-padding));
|
|
13
|
-
background: var(--functional-gray-extra-light);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.empty-bordered {
|
|
17
|
-
background: transparent;
|
|
18
|
-
border: 1px solid var(--functional-gray-light);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.empty-rounded {
|
|
22
|
-
border-radius: var(--functional-radius);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.empty-actions {
|
|
26
|
-
margin-block-start: var(--spacing-24);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
</style>
|
|
30
|
-
|
|
31
|
-
<script lang="ts">
|
|
32
|
-
export let isRounded = false;
|
|
33
|
-
export let isBordered = false;
|
|
34
|
-
const emptyClasses: string = [
|
|
35
|
-
"empty",
|
|
36
|
-
isRounded ? "empty-rounded" : "",
|
|
37
|
-
isBordered ? "empty-bordered" : "",
|
|
38
|
-
]
|
|
39
|
-
.filter((cl) => cl.length)
|
|
40
|
-
.join(" ");
|
|
41
|
-
</script>
|
|
42
|
-
|
|
43
|
-
<div class={emptyClasses}>
|
|
44
|
-
<slot name="header" />
|
|
45
|
-
<slot name="body" />
|
|
46
|
-
<div class="empty-actions">
|
|
47
|
-
<slot name="footer" />
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
declare const __propDef: {
|
|
3
|
-
props: {
|
|
4
|
-
isRounded?: boolean;
|
|
5
|
-
isBordered?: boolean;
|
|
6
|
-
};
|
|
7
|
-
events: {
|
|
8
|
-
[evt: string]: CustomEvent<any>;
|
|
9
|
-
};
|
|
10
|
-
slots: {
|
|
11
|
-
header: {};
|
|
12
|
-
body: {};
|
|
13
|
-
footer: {};
|
|
14
|
-
};
|
|
15
|
-
};
|
|
16
|
-
export type EmptyStateProps = typeof __propDef.props;
|
|
17
|
-
export type EmptyStateEvents = typeof __propDef.events;
|
|
18
|
-
export type EmptyStateSlots = typeof __propDef.slots;
|
|
19
|
-
export default class EmptyState extends SvelteComponentTyped<EmptyStateProps, EmptyStateEvents, EmptyStateSlots> {
|
|
20
|
-
}
|
|
21
|
-
export {};
|