@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/.eslintrc.cjs
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
root: true,
|
|
3
|
+
parser: '@typescript-eslint/parser',
|
|
4
|
+
extends: 'standard-with-typescript',
|
|
5
|
+
plugins: ['svelte3', '@typescript-eslint'],
|
|
6
|
+
ignorePatterns: ['*.cjs', 'package/**/*', 'svelte.config.js', 'playwright.config.js'],
|
|
7
|
+
overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }],
|
|
8
|
+
settings: {
|
|
9
|
+
'svelte3/typescript': () => require('typescript')
|
|
10
|
+
},
|
|
11
|
+
rules: {
|
|
12
|
+
'import/first': 'off',
|
|
13
|
+
'no-multiple-empty-lines': 'off',
|
|
14
|
+
'no-undef-init': 'off',
|
|
15
|
+
'no-unused-vars': 'off',
|
|
16
|
+
'no-use-before-define': 'off',
|
|
17
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
18
|
+
'@typescript-eslint/array-type': 'off',
|
|
19
|
+
'@typescript-eslint/no-non-null-assertion': 'off',
|
|
20
|
+
'@typescript-eslint/no-unused-vars': 'off', // typescript does this better
|
|
21
|
+
'@typescript-eslint/require-await': 'off',
|
|
22
|
+
'@typescript-eslint/return-await': ['error', 'always'],
|
|
23
|
+
'@typescript-eslint/strict-boolean-expressions': 'off',
|
|
24
|
+
'@typescript-eslint/prefer-readonly': ['off']
|
|
25
|
+
},
|
|
26
|
+
parserOptions: {
|
|
27
|
+
project: './tsconfig.json',
|
|
28
|
+
sourceType: 'module',
|
|
29
|
+
extraFileExtensions: ['.svelte'],
|
|
30
|
+
ecmaVersion: 2020
|
|
31
|
+
},
|
|
32
|
+
env: {
|
|
33
|
+
browser: true,
|
|
34
|
+
es2017: true,
|
|
35
|
+
node: true
|
|
36
|
+
}
|
|
37
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Collapsing Table
|
|
2
|
+
|
|
3
|
+
This component is meant to help make tables responsive by eliminating columns (from the right) as the screen width reduces. When there are hidden columns, the last column header becomes a dropdown button allowing the selection of another column to be displayed instead.
|
|
4
|
+
|
|
5
|
+
# Usage
|
|
6
|
+
|
|
7
|
+
The only required prop is an `items` array of objects. Column headers will automatically be detected from object keys if no column configuration is present.
|
|
8
|
+
|
|
9
|
+
## Column Configuration
|
|
10
|
+
|
|
11
|
+
A `config` prop allows you to pass an array of column definitions. If passed, only these columns will appear, other keys in your `items` objects will be ignored.
|
|
12
|
+
|
|
13
|
+
### Variables accepted for each column
|
|
14
|
+
|
|
15
|
+
* `key: string` corresponds to object key in your `items` objects
|
|
16
|
+
* `title?: string` a display name for this column's header
|
|
17
|
+
* `width?: number` a guess at how wide in pixels this column will be; helps decide when to hide
|
|
18
|
+
columns, default 150
|
|
19
|
+
* `neverhide?: boolean`<br>
|
|
20
|
+
setting to make sure this column is never hidden; good to set on
|
|
21
|
+
the column that is most identifying for each row, like a name.
|
|
22
|
+
by default the leftmost column will stick around as identifying
|
|
23
|
+
* `bodyCellClass?: string` CSS class to set on `td` elements in the body area
|
|
24
|
+
* `headerCellClass?: string` CSS class to set on `td` elements in the header row
|
|
25
|
+
|
|
26
|
+
## Optional Props
|
|
27
|
+
* `tableClass` CSS class for the `table`
|
|
28
|
+
* `bodyRowClass` CSS class for `tr` in the body area
|
|
29
|
+
* `bodyCellClass` CSS class for `td` in the body area, additional to the one set in column config
|
|
30
|
+
* `headerRowClass` CSS class for `tr` on the header row
|
|
31
|
+
* `headerCellClass` CSS class for `th` in the header row, additional to the one set in column config
|
|
32
|
+
* `menuClass` CSS class for the `ul` dropdown menu
|
|
33
|
+
* `menuItemClass` CSS class for `li` in the dropdown menu
|
|
34
|
+
* `menuItemHilitedClass` CSS class for `li` in the dropdown menu when they are currently hilited/active
|
|
35
|
+
* `defaultCellWidth`, guess for any column width in pixels, allows you to hide columns earlier/later, default 150
|
|
36
|
+
|
|
37
|
+
## Slots
|
|
38
|
+
Lots of slots are provided for extra configuration.
|
|
39
|
+
|
|
40
|
+
### Default Slot
|
|
41
|
+
The default slot is used for each table cell in the body area. It is optional and falls back to {value}.
|
|
42
|
+
Because of the way slots work, your slot content will be executed in every cell. So if you need to do
|
|
43
|
+
something special for just one cell, you have to use an if/else block to identify the special cell,
|
|
44
|
+
falling back to {value} for everything else:
|
|
45
|
+
```svelte
|
|
46
|
+
<CollapsingTable items={myItems} let:item let:key let:value>
|
|
47
|
+
{#if key === 'myspecialkey'}
|
|
48
|
+
<MySpecialComponent>{value}</MySpecialComponent>
|
|
49
|
+
{:else}
|
|
50
|
+
{value}
|
|
51
|
+
{/if}
|
|
52
|
+
</CollapsingTable>
|
|
53
|
+
```
|
|
54
|
+
The slot props are `item`, `key`, and `value`.
|
|
55
|
+
### Header Cell Slot
|
|
56
|
+
A slot named `headercell` allows you to do the same thing for cells in the header row that the
|
|
57
|
+
default slot allows for body cells.
|
|
58
|
+
```svelte
|
|
59
|
+
<CollapsingTable items={myItems} let:column let:key let:title>
|
|
60
|
+
<div slot="headercell">
|
|
61
|
+
{#if key === 'myspecialkey'}
|
|
62
|
+
<MySpecialComponent>{title}</MySpecialComponent>
|
|
63
|
+
{:else}
|
|
64
|
+
{title}
|
|
65
|
+
{/if}
|
|
66
|
+
</div>
|
|
67
|
+
</CollapsingTable>
|
|
68
|
+
```
|
|
69
|
+
The slot props are `column`, `key`, and `title`. `column` is the configuration object for the
|
|
70
|
+
current column, `title` is `column.title || column.key`. The title/key will be used as fallback
|
|
71
|
+
content if you do not specify a header slot.
|
|
72
|
+
### Dropdown Icon Slot
|
|
73
|
+
A slot is provided for you to provide a custom icon for the column header that is serving as the
|
|
74
|
+
dropdown button.
|
|
75
|
+
```svelte
|
|
76
|
+
<CollapsingTable items={myItems}>
|
|
77
|
+
<i type='fa caret-down' slot="dropicon" aria-hidden="true"></i>
|
|
78
|
+
</CollapsingTable>
|
|
79
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Conditional Wrapper
|
|
2
|
+
Sometimes you want to wrap a complex bit of HTML in a certain element based on a condition,
|
|
3
|
+
and not wrap it at all if the condition isn't met. Without help, this situation results in a
|
|
4
|
+
lot of duplicate code because you have to have an if/else block outside the whole thing, and
|
|
5
|
+
the complex HTML goes in both branches:
|
|
6
|
+
```svelte
|
|
7
|
+
{#if shouldwrap}
|
|
8
|
+
<div class="wrapper">
|
|
9
|
+
... a whole bunch of html ...
|
|
10
|
+
</div>
|
|
11
|
+
{:else}
|
|
12
|
+
... a whole bunch of html, again ...
|
|
13
|
+
{/if}
|
|
14
|
+
```
|
|
15
|
+
With this component, you can de-duplicate like this:
|
|
16
|
+
```svelte
|
|
17
|
+
<ConditionalWrapper class="wrapper" condition={shouldwrap}>
|
|
18
|
+
... a whole bunch of html ...
|
|
19
|
+
</ConditionalWrapper>
|
|
20
|
+
```
|
|
21
|
+
Any props you provide (except the ConditionalWrapper props like `condition`) will pass through
|
|
22
|
+
to the wrapper element/component, if it gets inserted.
|
|
23
|
+
|
|
24
|
+
## Props
|
|
25
|
+
* `condition: boolean|undefined` (required) - Whether or not to wrap the element. Accepts `undefined`
|
|
26
|
+
for convenience when evaluating presence of an optional variable.
|
|
27
|
+
* `component?: SvelteComponent` (optional) - The wrapper will be a svelte component instead of a DOM
|
|
28
|
+
element. Props all get passed through.
|
|
29
|
+
* `a: boolean` (optional) - The wrapper will be a link element.
|
|
30
|
+
* `span: boolean` (optional) - The wrapper will be a span element.
|
|
31
|
+
* `element?: HTMLElement` (for binding) - If the wrapper is a DOM element, bind this prop to get a
|
|
32
|
+
reference to wrapper element (if it exists). If the wrapper is a svelte component, this will be
|
|
33
|
+
passed to it as a prop so that the wrapper component can bind it to a DOM element.
|
|
34
|
+
|
|
35
|
+
If none of `a`, `span`, or `component` props are specified, the default is to create a `div` wrapper.
|
package/docs/Modal.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Modal
|
|
2
|
+
The Modal component is designed to block out the screen and focus the user on the content inside the Modal.
|
|
3
|
+
|
|
4
|
+
It provides only the backdrop and a scrollable container for your content. If your content should have a background color, be sure to add it yourself.
|
|
5
|
+
|
|
6
|
+
Any time the Modal is in the DOM, it will take over the screen. You make it go away by removing it from the DOM.
|
|
7
|
+
|
|
8
|
+
## Example Usage
|
|
9
|
+
```svelte
|
|
10
|
+
<script>
|
|
11
|
+
let showMyModal = false
|
|
12
|
+
</script>
|
|
13
|
+
<button on:click={() => showMyModal = true}>Show Modal</button>
|
|
14
|
+
{#if showMyModal}
|
|
15
|
+
<Modal on:escape={() => showMyModal = false}>
|
|
16
|
+
This is a modal dialog!
|
|
17
|
+
</Modal>
|
|
18
|
+
{/if}
|
|
19
|
+
<style>
|
|
20
|
+
:global(.modal-container) { background: white; }
|
|
21
|
+
</style>
|
|
22
|
+
```
|
|
23
|
+
## Custom Events
|
|
24
|
+
* `on:escape` - dispatched when the user does something that would normally dismiss a modal, such as clicking in the darkened backdrop or pressing the Escape key. You should listen for this and dismiss your modal. If you do not listen for this event, users will only be able to escape by some other means provided within your content. Be careful not to trap them!
|
|
25
|
+
|
|
26
|
+
## Props
|
|
27
|
+
* `opaque: boolean` (default `false`) - Make the backdrop completely opaque so that it cannot be seen through. This is a more jarring experience for users but sometimes you don't want background content to be visible at all.
|
|
28
|
+
* `containerClass: string` (default `''`) - A class to be added to the scrollable container inside the backdrop. This container will always have the `.modal-container` class regardless of this prop.
|
|
29
|
+
* `escapable: boolean` (default `true`) - Let screen readers know that they can escape the modal by pressing the Escape key. If you are not listening for `on:escape` then you should set this `false` to avoid giving screen reader users false information.
|
|
30
|
+
* `hidefocus: boolean` (default `true`) - Focus must be moved to the modal when it opens, in order to maintain accessibility. This component avoids making the focus visible to users by default as it might be visually disconcerting. So on mount, focus is moved to an invisible element above your content. If you prefer focus to be visible to users, set this to `false` and focus will automatically be moved to the first element in your content that has a tabindex and therefore be visible to users. This prop is overridden if you provide an `initialfocus`.
|
|
31
|
+
* `hidefocuslabel: string` (default `focus is above modal dialog, start tabbing`) - The message that will be read out to users using a screen reader when the focus is on the invisible element created by the `hidefocus` prop.
|
|
32
|
+
* `initialfocus: string` (default `undefined`) - A selector to be used to locate and set focus on an element in your content, on mount. Use if you want focus on a specific element when the modal opens. Overrides `hidefocus`.
|
|
33
|
+
* `returnfocusto: HTMLElement` (default element that had focus was when modal opened) - Use to control where focus moves when the modal closes. If not provided, component automatically saves the element that had focus on mount. Usually it's the button that triggered the modal.
|
|
34
|
+
|
|
35
|
+
# FocusLock
|
|
36
|
+
The FocusLock component is for creating accessible modal dialogs that do not have a darkened backdrop. It shares many props and behaviors with Modal because the Modal component uses a FocusLock itself.
|
|
37
|
+
|
|
38
|
+
Any time the FocusLock is in the DOM, it will lock screen readers inside it. You release them by removing it from the DOM. Regular users are NOT trapped and may interact with other screen elements. When they do, the `escape` event will be fired, allowing you to remove the dialog.
|
|
39
|
+
|
|
40
|
+
FocusLocks can be nested. The user will be trapped inside the deepest FocusLock present in the DOM. When it goes away, they'll be trapped inside the previous FocusLock, and so on.
|
|
41
|
+
|
|
42
|
+
## Custom Events
|
|
43
|
+
* `on:escape` - dispatched when the user does something to dismiss the dialog, such as pressing the Escape key or clicking outside the dialog area. You should listen for this and remove the FocusLock. If you do not listen for this event, users will only be able to escape by some other means provided within your content. Be careful not to trap them!
|
|
44
|
+
|
|
45
|
+
## Example Usage
|
|
46
|
+
```svelte
|
|
47
|
+
<script>
|
|
48
|
+
let showMyDialog = false
|
|
49
|
+
</script>
|
|
50
|
+
<button on:click={() => showMyDialog = true}>Show Dialog</button>
|
|
51
|
+
{#if showMyDialog}
|
|
52
|
+
<FocusLock on:escape={() => showMyDialog = false}>
|
|
53
|
+
Screen readers cannot escape this!
|
|
54
|
+
<button on:click={() => showMyDialog = false}>Collapse Dialog</button>
|
|
55
|
+
</FocusLock>
|
|
56
|
+
{/if}
|
|
57
|
+
```
|
|
58
|
+
## Props
|
|
59
|
+
* `escapable: boolean` (default `true`) - Controls whether the focus lock can be easily escaped by pressing the Escape key or clicking the invisible focus (see `hidefocus`). If you set this `false` you must provide a way to dismiss the lock inside your content or screen readers will be trapped.
|
|
60
|
+
* `hidefocus: boolean` (default `true`) - Focus must be moved to the modal when it opens, in order to maintain accessibility. This component avoids making the focus visible to users by default as it might be visually disconcerting. So on mount, focus is moved to an invisible element above your content. If you prefer focus to be visible to users, set this to `false` and focus will automatically be moved to the first element in your content that has a tabindex and therefore be visible to users. This prop is overridden if you provide an `initialfocus`.
|
|
61
|
+
* `hidefocuslabel: string` (default `focus is above modal dialog, start tabbing`) - The message that will be read out to users using a screen reader when the focus is on the invisible element created by the `hidefocus` prop.
|
|
62
|
+
* `initialfocus: string` (default `undefined`) - A selector to be used to locate and set focus on an element in your content, on mount. Use if you want focus on a specific element when the lock mounts. Overrides `hidefocus`.
|
|
63
|
+
* `returnfocusto: HTMLElement` (default element that had focus was when focuslock mounted) - Use to control where focus moves when the lock ends. If not provided, component automatically saves the element that had focus on mount. Usually it's the button that triggered the dialog.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# PopupMenu
|
|
2
|
+
|
|
3
|
+
This component is meant to display a menu of options to the user, activated by clicking
|
|
4
|
+
any element. It can also be controlled from the parent, by binding the `menushown` boolean.
|
|
5
|
+
|
|
6
|
+
## Props
|
|
7
|
+
* `buttonelement: HTMLElement` (required) - The DOM element that will act as the "button" for this menu. The menu will be placed next to the button and be controlled by the button. Keyboard access will be enabled when the button has focus. This component adds all appropriate attributes (tabindex, roles, and aria) automatically.
|
|
8
|
+
* `items: PopupMenuItem[]` (required) - The list of menu items to be shown. Parent may change this at any time based on user activity.
|
|
9
|
+
* `menushown?: boolean` (default `false`) - Bind this prop to track/change the visible state of the menu from the parent.
|
|
10
|
+
* `value?: string` (default no selection) - Bind this prop to track/change the currently selected value from the parent.
|
|
11
|
+
* `align?: GlueAlignOpts` (default `auto`) - Control where the menu will appear. Default is to use the current viewport to make a decision to maximize potential for menu growth.
|
|
12
|
+
* `cover?: boolean` (default `false`) - When the menu is active, should it cover the button or be rendered above/below it?
|
|
13
|
+
* `showSelected?: boolean` (default `true`) - When an item is currently selected, should it appear in the menu (with a visual indicator), or simply be removed from the list (showSelected: `false`)?
|
|
14
|
+
* `width?: string` (default `undefined`) - When defined, this width will be passed through to the menu's CSS width. Use a valid CSS dimension such as `33em` or `159px`. Generally this will be used when you need to match the menu width to something else, like the button. If the width is static like `100%` a simple CSS rule is likely more efficient.
|
|
15
|
+
* `computedalign: readable<{ valign: 'top'|'bottom', halign: 'left'|'right' }>` (default store is provided) - Bind this prop to receive a store that is updated each time a menu placement decision is made. Example of when this is useful is when you are trying to round corners of your button and need to know whether the menu is above or below your button so you know which corners to round and which to leave square. Note that the values do not update when the menu is hidden, so you may need to rely on `menushown` as well.
|
|
16
|
+
* `usePortal?: HTMLElement|true` (default `false`) - If the menu would be clipped by an `overflow: hidden`, you can set this prop and it will be placed in the specified container, or the `document.body` if you simply say `true`. Placement will still be calculated correctly, unless there are scrolling containers between the button and the menu.
|
|
17
|
+
* `emptyText?: string` (default `¯\_(ツ)_/¯`) - When there are no items (e.g. it's a filtered search and there were no results), we still display one disabled item in the menu to let the user know what is going on. Use this prop to specify the message.
|
|
18
|
+
* `menuContainerClass?: string` (default `''`) - CSS class to pass through so you can do extra styling work
|
|
19
|
+
* `menuClass?: string` (default `''`) - CSS class to pass through so you can do extra styling work
|
|
20
|
+
* `menuItemClass?: string` (default `''`) - CSS class to pass through so you can do extra styling work
|
|
21
|
+
* `menuItemHilitedClass?: string` (default `''`) - CSS class to pass through so you can do extra styling work
|
|
22
|
+
* `menuItemSelectedClass?: string` (default `''`) - CSS class to pass through so you can do extra styling work
|
|
23
|
+
|
|
24
|
+
## Events
|
|
25
|
+
* `on:change` - Dispatched any time a new value is selected. Does not fire when you change the `value` property from the outside. Event detail will be the full PopupMenuItem object that was selected.
|
|
26
|
+
|
|
27
|
+
## Types
|
|
28
|
+
* `PopupMenuItem: { value: string, label?: string, disabled?: boolean }` - Each item must have a unique non-empty value. Display label is optional, as is a `disabled` status that will show the item in the list but not allow users to hilite or select it.
|
|
29
|
+
|
|
30
|
+
## Disabling
|
|
31
|
+
Since there are so many different possibilities for the button you attach to, the PopupMenu does not have a `disabled` prop. If the PopupMenu should be disabled, simply remove it from the DOM with an `{#if}` block. Remember to also set `aria-disabled` on your button and likely provide some visual indicators like `cursor: default` or the `disabled` attribute on elements that support it.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# ScreenReaderOnly
|
|
2
|
+
This component is for adding text that can be read to screen readers while remaining
|
|
3
|
+
invisible to regular users. It accepts `arialive` and `ariaatomic` props to pass through to
|
|
4
|
+
the corresponding HTML attributes. It also accepts an `id` prop in case you need to
|
|
5
|
+
point at it with an aria-describedby attribute on some other element.
|
|
6
|
+
|
|
7
|
+
Using this component is often better than using aria-label because aria-label still has poor support
|
|
8
|
+
and sometimes is not good enough to make WAVE happy with an empty icon-link. It's also a
|
|
9
|
+
good way to add additional text when sighted users have a little text already, e.g. to add
|
|
10
|
+
interactivity instructions.
|
package/docs/actions.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Actions
|
|
2
|
+
|
|
3
|
+
## use:buttonify
|
|
4
|
+
This action turns any HTML element into an accessible button that responds to keyboard events and has
|
|
5
|
+
role="button" and the tabindex attribute set. You are still responsible for making sure it has
|
|
6
|
+
readable content or a good label. Does NOT change styling at all.
|
|
7
|
+
```svelte
|
|
8
|
+
<div use:buttonify on:click={onClick}>You can tab to me and click me with the Enter key</div>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## use:glue
|
|
12
|
+
This action glues the element it is placed on to the target specified, with a specified alignment
|
|
13
|
+
option. It is most useful for dropdown or autocomplete menus.
|
|
14
|
+
|
|
15
|
+
The element should have `position: absolute` on it already. It is not moved to a new place in the DOM,
|
|
16
|
+
it just gets `top`/`left`/`bottom`/`right` properties set.
|
|
17
|
+
|
|
18
|
+
If you have problems with the glued element being clipped by a parent with `overflow: hidden`, you
|
|
19
|
+
may combine it with the `use:portal` action to move it somewhere else; `use:glue` will recalculate
|
|
20
|
+
for the new location.
|
|
21
|
+
|
|
22
|
+
If you do not specify an `align` setting, the default is `auto`, which will evaluate the current
|
|
23
|
+
viewport and determine which alignment gives the glued element the most space to grow.
|
|
24
|
+
```svelte
|
|
25
|
+
<button bind:this={menubutton}>Activate Menu</button>
|
|
26
|
+
<ul use:glue={{ target: menubutton, align: 'bottomright' }}>
|
|
27
|
+
<li>Menu Item 1</li>
|
|
28
|
+
<li>Menu Item 2</li>
|
|
29
|
+
</ul>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## use:offset
|
|
33
|
+
This action allows you to observe any DOM element's position in the body. You pass in a svelte store
|
|
34
|
+
and it will be updated when the element moves.
|
|
35
|
+
|
|
36
|
+
Another configuration option is `debounce: number`, which will debounce the updates for the specified
|
|
37
|
+
number of milliseconds. Use this option if you will make a complicated calculation after each update and do not
|
|
38
|
+
want to perform the calculation multiple times during a single animation or while the user is resizing
|
|
39
|
+
their viewport/window. It will defer the updates until the animation or resizing stops for at least `debounce`
|
|
40
|
+
milliseconds.
|
|
41
|
+
```svelte
|
|
42
|
+
<script>
|
|
43
|
+
const offset = writable({ top: 0, left: 0, bottom: 0, right: 0 })
|
|
44
|
+
</script>
|
|
45
|
+
<div use:offset={{ store: offset }}>distance between me and body top is {$offset.top}</div>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## use:portal
|
|
49
|
+
This action allows you to move an element to a new place in the DOM upon mount. It's useful when your component
|
|
50
|
+
is sensitive to getting clipped by parents with overflow: hidden or for anything like a modal or toast message
|
|
51
|
+
that needs to be positioned relative to the body, but created within a smaller component, like a web form.
|
|
52
|
+
|
|
53
|
+
For convenience, the default is to move it to `document.body` when you `use:portal` without any config:
|
|
54
|
+
```svelte
|
|
55
|
+
<div use:portal>I'm in the body now!</div>
|
|
56
|
+
```
|
|
57
|
+
If you are accepting an optional target as a prop, this may cause unexpected behavior where your parent component
|
|
58
|
+
declines to pass a portal target, but your element ends up in `document.body`. To prevent this, you may pass `null`
|
|
59
|
+
instead of `undefined`, and the element will stay where it was before the change to `null`:
|
|
60
|
+
```svelte
|
|
61
|
+
<script>
|
|
62
|
+
export let portaltarget = undefined
|
|
63
|
+
</script>
|
|
64
|
+
<div use:portal={portaltarget || null}></div>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## use:resize
|
|
68
|
+
This action allows you to observe an element's width and height with the `ResizeObserver`. Svelte has a built-in
|
|
69
|
+
`bind:clientWidth` functionality, but it works by inserting an invisible iframe into the DOM for each observation,
|
|
70
|
+
which is a little ugly and potentially slow.
|
|
71
|
+
|
|
72
|
+
You pass in a svelte store and it will be updated when the element's size changes.
|
|
73
|
+
|
|
74
|
+
Another configuration option is `debounce: number`, which will debounce the updates for the specified
|
|
75
|
+
number of milliseconds. Use this option if you will make a complicated calculation after each update and do not
|
|
76
|
+
want to perform the calculation multiple times during a single animation or while the user is resizing
|
|
77
|
+
their viewport/window. It will defer the updates until the animation or resizing stops for at least `debounce`
|
|
78
|
+
milliseconds.
|
|
79
|
+
```svelte
|
|
80
|
+
<script>
|
|
81
|
+
const size = writable({ clientWidth: 100, clientHeight: 100, offsetWidth: 100, offsetHeight: 100 })
|
|
82
|
+
</script>
|
|
83
|
+
<div use:offset={{ store: size }}>My dimensions are {$size.offsetWidth}x{$size.offsetHeight}</div>
|
|
84
|
+
```
|
package/docs/stores.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Overview
|
|
2
|
+
A set of stores to complement svelte stores. These stores perform deep equality checks on the data before updating subscribers, and are easy to subclass and add mutation methods.
|
|
3
|
+
|
|
4
|
+
## Subclassing
|
|
5
|
+
The most effective way to use a store is to create a Store subclass with mutation
|
|
6
|
+
methods. If you're familiar with redux, these methods would be similar to actions on
|
|
7
|
+
reducers.
|
|
8
|
+
```typescript
|
|
9
|
+
interface MyState {
|
|
10
|
+
foo: string
|
|
11
|
+
more: string
|
|
12
|
+
}
|
|
13
|
+
class MyStore extends Store<MyState> {
|
|
14
|
+
updateFoo (newfoo: string) {
|
|
15
|
+
// do some business logic here
|
|
16
|
+
newfoo = newfoo.trim().toLocaleLowerCase()
|
|
17
|
+
this.set({ ...this.value, foo: newfoo })
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const store = new MyStore({ foo: 'bar', more: 'state' })
|
|
21
|
+
store.updateFoo('baz')
|
|
22
|
+
```
|
|
23
|
+
This way your store controls all the business logic and your components can be truly
|
|
24
|
+
reactive.
|
|
25
|
+
|
|
26
|
+
Note that `this.value` contains the current version of the store. You can also use the
|
|
27
|
+
`store.update` method to have the current value passed to you.
|
|
28
|
+
|
|
29
|
+
### Asynchronous mutations
|
|
30
|
+
Mutation methods become even more powerful when you do asynchronous updates. All of
|
|
31
|
+
your asynchronous code can be encapsulated in a single method, making it much easier
|
|
32
|
+
to think about how you want to handle race conditions and errors. Redux toolkit would
|
|
33
|
+
call these `thunk` functions, but here they are just another method.
|
|
34
|
+
```typescript
|
|
35
|
+
interface BookState {
|
|
36
|
+
loading?: boolean
|
|
37
|
+
book?: Book
|
|
38
|
+
error?: string
|
|
39
|
+
}
|
|
40
|
+
class BookStore extends Store<BookState> {
|
|
41
|
+
async getBook (id: number) {
|
|
42
|
+
if (this.value.loading) return // one book at a time!
|
|
43
|
+
this.set({ loading: true, book: undefined, error: undefined })
|
|
44
|
+
try {
|
|
45
|
+
const book = await api.getBook(id)
|
|
46
|
+
this.set({ loading: false, book, error: undefined })
|
|
47
|
+
} catch (e) {
|
|
48
|
+
this.set({ loading: false, book: undefined, error: e.message })
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Creating and sharing store instances
|
|
55
|
+
It's up to you to determine how you'd like to create and share stores. The singleton
|
|
56
|
+
pattern works great for end user applications. For instance, a global error store can
|
|
57
|
+
help individual components communicate with the layout component when they experience
|
|
58
|
+
a problem that's fatal for the page. Creating a singleton store for each screen in your
|
|
59
|
+
application is another powerful pattern and helps all your components share the screen
|
|
60
|
+
state.
|
|
61
|
+
|
|
62
|
+
Context can be very convenient as well. The main benefit of putting a store in Context
|
|
63
|
+
is it can be garbage collected when the context owner is unmounted. In SSR scenarios,
|
|
64
|
+
global stores can leak between clients unless you're careful to reset them on each request,
|
|
65
|
+
so storing in Context can be safer.
|
|
66
|
+
|
|
67
|
+
Similarly, a store can be created in a component and passed down as a prop, enabling
|
|
68
|
+
efficient two-way communication with its children. The main advantage over binding a
|
|
69
|
+
property is that the components can share the mutation methods.
|
|
70
|
+
|
|
71
|
+
## Store Types
|
|
72
|
+
|
|
73
|
+
### Store
|
|
74
|
+
This is the basic store type and performs deep equality checks before notifying subscribers.
|
|
75
|
+
It is intended to contain simple JSON state that is easy to programmatically explore. By
|
|
76
|
+
default, you are expected to update the store with fresh or cloned objects:
|
|
77
|
+
```typescript
|
|
78
|
+
const initialState = { foo: 'bar', more: 'state' }
|
|
79
|
+
const store = new Store(initialState)
|
|
80
|
+
store.update(state => ({ ...state, foo: 'baz' }))
|
|
81
|
+
```
|
|
82
|
+
### SafeStore
|
|
83
|
+
If it's not convenient to clone your objects, you can use a `SafeStore` instead,
|
|
84
|
+
which will allow you to mutate your state and then call `set` on it:
|
|
85
|
+
```typescript
|
|
86
|
+
const initialState = { foo: 'bar', more: 'state' }
|
|
87
|
+
const store = new SafeStore(initialState)
|
|
88
|
+
initialState.foo = 'baz'
|
|
89
|
+
store.set(initialState) // with a regular Store, this would not notify subscribers
|
|
90
|
+
```
|
|
91
|
+
This is only designed to work on pure JSON (objects and arrays, not Maps, Sets, or
|
|
92
|
+
any other data types). There is a slight performance cost due to deep cloning.
|
|
93
|
+
|
|
94
|
+
### ActiveStore
|
|
95
|
+
This is a store that does no equality checks before notifying subscribers. It can save a couple
|
|
96
|
+
CPU cycles by skipping the deep equality check, but may be an overall performance degradation
|
|
97
|
+
if subscribers are doing significant work and there are many updates that do not change the state.
|
|
98
|
+
It is also vulnerable to unexpected infinite loops if subscribers end up triggering a redundant
|
|
99
|
+
`set` command. This store is safe to use with mutated objects and is the only one that can
|
|
100
|
+
handle non-JSON objects in the state.
|
|
101
|
+
|
|
102
|
+
### SubStore
|
|
103
|
+
This is a store that makes it convenient to interact with a portion of a larger store. You
|
|
104
|
+
subscribe to a parent store in the constructor, and when you update the SubStore, your
|
|
105
|
+
changes will be sent to the parent store.
|
|
106
|
+
|
|
107
|
+
#### Getter / Setter version of SubStore
|
|
108
|
+
This requires both a getter and setter function to handle each direction. The setter is given
|
|
109
|
+
the new SubStore state and the existing parent store state and should return a new state for
|
|
110
|
+
the parent store.
|
|
111
|
+
```typescript
|
|
112
|
+
const parentStore = new Store({
|
|
113
|
+
about: { header: 'About' },
|
|
114
|
+
events: { header: 'Events' }
|
|
115
|
+
})
|
|
116
|
+
const aboutStore = new SubStore(parentStore, pState => pState.about, (newAboutState, pState) => ({ ...pState, about: newAboutState }))
|
|
117
|
+
aboutStore.set({ header: 'About Us' })
|
|
118
|
+
// aboutStore state is { header: 'About Us' }
|
|
119
|
+
// parentStore state is { about: { header: 'About Us' }, events: { header: 'Events' } }
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### Accessor string version of SubStore
|
|
123
|
+
Alternatively, a dot-prop accessor string can be provided and both the getter and setter will be
|
|
124
|
+
automatically generated.
|
|
125
|
+
```typescript
|
|
126
|
+
const parentStore = new Store({
|
|
127
|
+
about: { header: 'About' },
|
|
128
|
+
events: { header: 'Events' }
|
|
129
|
+
})
|
|
130
|
+
const aboutStore = subStore(parentStore, 'about')
|
|
131
|
+
aboutStore.set({ header: 'About Us' })
|
|
132
|
+
// aboutStore state is { header: 'About Us' }
|
|
133
|
+
// parentStore state is { about: { header: 'About Us' }, events: { header: 'Events' } }
|
|
134
|
+
```
|
|
135
|
+
Note the use of a `subStore` function rather than `new SubStore`. `new SubStore` will work just
|
|
136
|
+
fine, but since typescript (at the time of writing) does not allow method overloads on the
|
|
137
|
+
constructor, there was no way to automatically derive types. Make a habit of using the `subStore`
|
|
138
|
+
function if you use typescript.
|
|
139
|
+
|
|
140
|
+
#### Subclassing SubStore
|
|
141
|
+
If you want to create a class and mutation methods for your SubStore, you'll want to create an
|
|
142
|
+
interface for your parent type that requires a minimum interface of the part you deal with. Then
|
|
143
|
+
you create a custom constructor with your accessor or setter/getter.
|
|
144
|
+
```typescript
|
|
145
|
+
interface AboutType {
|
|
146
|
+
header: string
|
|
147
|
+
}
|
|
148
|
+
interface HasAboutType {
|
|
149
|
+
about: AboutType
|
|
150
|
+
}
|
|
151
|
+
class AboutStore<ParentType extends HasAboutType> extends SubStore<AboutType, ParentType> {
|
|
152
|
+
constructor (parentStore: WritableSubject<ParentType>) {
|
|
153
|
+
super(parentStore, 'about')
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
updateHeader (newHeader: string) {
|
|
157
|
+
this.update(state => ({ ...state, header: newHeader }))
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const parentStore = new Store({
|
|
161
|
+
about: { header: 'About' },
|
|
162
|
+
events: { header: 'Events' }
|
|
163
|
+
})
|
|
164
|
+
const aboutStore = new AboutStore(parentStore)
|
|
165
|
+
aboutStore.updateHeader('About Us')
|
|
166
|
+
```
|
|
167
|
+
Note the use of `WritableSubject` in the constructor. This is exported from our library, and is
|
|
168
|
+
a generic way of accepting any kind of store - it accepts anything with a `subscribe` and `update`
|
|
169
|
+
method. So you can write a SubStore that accepts default svelte stores as well as one of our stores.
|
|
170
|
+
|
|
171
|
+
### derivedStore
|
|
172
|
+
`derivedStore` is designed to allow you to compile data from one or more parent stores into another
|
|
173
|
+
read-only store. Each time any parent store changes, we'll run a function to derive a new state from
|
|
174
|
+
the parent state(s). This is extremely similar to svelte's `derived` store, but subscribers will only
|
|
175
|
+
be notified if the derived state has changed (uses a deep-equal instead of svelte's shallow equality).
|
|
176
|
+
```typescript
|
|
177
|
+
const num1Store = new Store(5)
|
|
178
|
+
const num2Store = new Store(7)
|
|
179
|
+
const sumStore = derivedStore([num1Store, num2Store], ([num1, num2]) => num1 + num2)
|
|
180
|
+
// sumStore state is 12
|
|
181
|
+
num1Store.set(3)
|
|
182
|
+
// sumStore state is 10
|
|
183
|
+
```
|
|
184
|
+
If you only use one parent store, it's possible to use a dot-prop string as the getter, basically a
|
|
185
|
+
read-only version of SubStore.
|
|
186
|
+
|
|
187
|
+
### convertStore
|
|
188
|
+
`convertStore` is provided to add deep equality checks to a svelte store. The state is duplicated into
|
|
189
|
+
one of our stores, but the duplicate is kept in sync with the parent svelte store and vice-versa. You can
|
|
190
|
+
feel free to `set` and `update` the converted store even if some of your code still uses the parent svelte
|
|
191
|
+
store.
|
|
192
|
+
```typescript
|
|
193
|
+
const svelteStore = writable({ putit: 'there' })
|
|
194
|
+
const deepEqualStore = convertStore(svelteStore)
|
|
195
|
+
svelteStore.set({ putit: 'here' })
|
|
196
|
+
// both stores have state { putit: 'here' }
|
|
197
|
+
deepEqualStore.set({ putit: 'there' })
|
|
198
|
+
// both stores are back to { putit: 'there' }
|
|
199
|
+
deepEqualStore.set({ putit: 'there' })
|
|
200
|
+
// no subscribers are notified, because deepEqualStore caught that nothing was updated
|
|
201
|
+
svelteStore.set({ putit: 'there' })
|
|
202
|
+
// svelteStore subscribers are notified, but deepEqualStore subscribers are not
|
|
203
|
+
```
|
package/package.json
CHANGED
|
@@ -1,57 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@txstate-mws/svelte-components",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Svelte components that are generically useful.",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "svelte-kit dev",
|
|
7
|
+
"build": "svelte-kit build",
|
|
8
|
+
"package": "svelte-kit package",
|
|
9
|
+
"preview": "svelte-kit preview",
|
|
10
|
+
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
11
|
+
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
|
12
|
+
"lint": "eslint --ignore-path .gitignore .",
|
|
13
|
+
"test": "playwright test tests"
|
|
14
|
+
},
|
|
5
15
|
"dependencies": {
|
|
6
16
|
"@txstate-mws/svelte-store": "^1.0.3",
|
|
7
17
|
"tabbable": "^5.2.1",
|
|
8
18
|
"txstate-utils": "^1.6.0"
|
|
9
19
|
},
|
|
10
20
|
"devDependencies": {
|
|
11
|
-
"@
|
|
21
|
+
"@playwright/test": "^1.18.1",
|
|
12
22
|
"@sveltejs/adapter-auto": "next",
|
|
13
23
|
"@sveltejs/kit": "next",
|
|
14
|
-
"@testing-library/jest-dom": "^5.16.1",
|
|
15
|
-
"@testing-library/svelte": "^3.0.3",
|
|
16
|
-
"@testing-library/user-event": "^13.5.0",
|
|
17
24
|
"@types/tabbable": "^3.1.2",
|
|
18
25
|
"eslint-config-standard-with-typescript": "^21.0.1",
|
|
19
26
|
"eslint-plugin-svelte3": "^3.2.1",
|
|
20
27
|
"svelte": "^3.44.0",
|
|
21
|
-
"svelte2tsx": "^0.5.1",
|
|
22
28
|
"svelte-check": "^2.2.6",
|
|
23
|
-
"svelte-jester": "^2.3.1",
|
|
24
29
|
"svelte-preprocess": "^4.9.4",
|
|
25
|
-
"
|
|
30
|
+
"svelte2tsx": "^0.5.2",
|
|
26
31
|
"tslib": "^2.3.1",
|
|
27
32
|
"typescript": "^4.4.3"
|
|
28
33
|
},
|
|
29
|
-
"type": "module"
|
|
30
|
-
|
|
31
|
-
"./package.json": "./package.json",
|
|
32
|
-
"./actions/buttonify": "./actions/buttonify.js",
|
|
33
|
-
"./actions/glue": "./actions/glue.js",
|
|
34
|
-
"./actions": "./actions/index.js",
|
|
35
|
-
"./actions/offset": "./actions/offset.js",
|
|
36
|
-
"./actions/portal": "./actions/portal.js",
|
|
37
|
-
"./actions/resize": "./actions/resize.js",
|
|
38
|
-
"./actions/sticky": "./actions/sticky.js",
|
|
39
|
-
"./actions/stickyfixed": "./actions/stickyfixed.js",
|
|
40
|
-
"./components/CollapsingTable.svelte": "./components/CollapsingTable.svelte",
|
|
41
|
-
"./components/ConditionalWrapper.svelte": "./components/ConditionalWrapper.svelte",
|
|
42
|
-
"./components/FocusLock.svelte": "./components/FocusLock.svelte",
|
|
43
|
-
"./components/Modal.svelte": "./components/Modal.svelte",
|
|
44
|
-
"./components/PopupMenu.svelte": "./components/PopupMenu.svelte",
|
|
45
|
-
"./components/ScreenReaderOnly.svelte": "./components/ScreenReaderOnly.svelte",
|
|
46
|
-
"./components": "./components/index.js",
|
|
47
|
-
".": "./index.js",
|
|
48
|
-
"./types/CollapsingTable": "./types/CollapsingTable.js",
|
|
49
|
-
"./types/PopupMenu": "./types/PopupMenu.js",
|
|
50
|
-
"./types": "./types/index.js",
|
|
51
|
-
"./util/bodyoffset": "./util/bodyoffset.js",
|
|
52
|
-
"./util": "./util/index.js",
|
|
53
|
-
"./util/repositioned": "./util/repositioned.js",
|
|
54
|
-
"./util/util": "./util/util.js"
|
|
55
|
-
},
|
|
56
|
-
"svelte": "./index.js"
|
|
57
|
-
}
|
|
34
|
+
"type": "module"
|
|
35
|
+
}
|