@vollowx/seele 0.12.0 → 0.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -23
- package/package.json +10 -10
- package/src/base/autocomplete.js +19 -3
- package/src/base/list.js +245 -0
- package/src/base/menu.js +5 -5
- package/src/m3/autocomplete/autocomplete.js +2 -0
- package/src/m3/focus-ring/focus-ring-styles.css.js +1 -1
- package/src/m3/list/list-styles.css.js +1 -1
- package/src/m3/list/list.js +11 -2
package/README.md
CHANGED
|
@@ -1,38 +1,29 @@
|
|
|
1
1
|
# Standard Extensible Elements
|
|
2
2
|
|
|
3
|
-
**SEELE** is a modern, lightweight
|
|
3
|
+
**SEELE** is a modern, lightweight and accessible
|
|
4
|
+
[Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components)
|
|
5
|
+
library. It provides a set of highly customizable UI components that follow the
|
|
6
|
+
[Material Design 3](https://m3.material.io/) guidelines out of the box, while
|
|
7
|
+
being designed for easy extension and restyling.
|
|
4
8
|
|
|
5
9
|
Visit the [website of SEELE](https://seele.v9.nz/) for documentation and demos.
|
|
6
10
|
|
|
7
|
-
## Features
|
|
8
|
-
|
|
9
|
-
- **Material Design 3**: Ready-to-use components following the latest Material guidelines.
|
|
10
|
-
- **Web Components**: Framework-agnostic. Works with vanilla HTML or any framework.
|
|
11
|
-
- **Extensible**: Built to be extended. Create your own design system on top of SEELE's logic.
|
|
12
|
-
- **Lightweight**: Built on [Lit](https://lit.dev/) and [floating-ui](https://floating-ui.com/) only, ensuring fast performance and small bundle sizes.
|
|
13
|
-
- **Accessible**: Designed with accessibility in mind (using `ElementInternals` and standard ARIA patterns).
|
|
14
|
-
|
|
15
11
|
## Installation
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
SEELE is published on [npm](https://www.npmjs.com/package/@vollowx/seele),
|
|
14
|
+
install with your preferred package manager:
|
|
18
15
|
|
|
19
16
|
```bash
|
|
20
|
-
# npm
|
|
21
17
|
npm install @vollowx/seele
|
|
22
18
|
|
|
23
|
-
# pnpm
|
|
24
|
-
pnpm add @vollowx/seele
|
|
25
|
-
|
|
26
|
-
# yarn
|
|
27
19
|
yarn add @vollowx/seele
|
|
28
20
|
|
|
29
|
-
# bun
|
|
30
21
|
bun add @vollowx/seele
|
|
31
22
|
```
|
|
32
23
|
|
|
33
24
|
## Usage
|
|
34
25
|
|
|
35
|
-
### Importing
|
|
26
|
+
### Importing
|
|
36
27
|
|
|
37
28
|
You can import the entire library or individual components to keep your bundle size small.
|
|
38
29
|
|
|
@@ -40,14 +31,15 @@ You can import the entire library or individual components to keep your bundle s
|
|
|
40
31
|
// Import all components
|
|
41
32
|
import '@vollowx/seele';
|
|
42
33
|
|
|
43
|
-
//
|
|
34
|
+
// Or import specific components (recommended)
|
|
35
|
+
// They all follow such path :@/catagory/group/component.js
|
|
44
36
|
import '@vollowx/seele/m3/button/common-button.js';
|
|
45
37
|
import '@vollowx/seele/m3/checkbox/checkbox.js';
|
|
46
38
|
```
|
|
47
39
|
|
|
48
|
-
### Using
|
|
40
|
+
### Using
|
|
49
41
|
|
|
50
|
-
Once imported,
|
|
42
|
+
Once imported, the components can be used just like standard HTML elements.
|
|
51
43
|
|
|
52
44
|
```html
|
|
53
45
|
<md-button variant="filled">Filled Button</md-button>
|
|
@@ -61,9 +53,15 @@ Once imported, use the components just like standard HTML tags.
|
|
|
61
53
|
|
|
62
54
|
### Theming
|
|
63
55
|
|
|
64
|
-
SEELE components use CSS variables for styling.
|
|
56
|
+
SEELE components use CSS variables for styling.
|
|
57
|
+
|
|
58
|
+
Currently, Material Design 3 token variables are not yet included in the source
|
|
59
|
+
code.
|
|
65
60
|
|
|
66
|
-
To style the components correctly, you need to define the necessary CSS
|
|
61
|
+
To style the components correctly, you need to define the necessary CSS
|
|
62
|
+
variables in your project. You can find reference implementations in
|
|
63
|
+
[vollowx/seele-docs](https://github.com/vollowx/seele-docs/) or the `dev` folder
|
|
64
|
+
of this repository.
|
|
67
65
|
|
|
68
66
|
## Browser Supporty
|
|
69
67
|
|
|
@@ -75,5 +73,5 @@ SEELE relies on modern web standards like `ElementInternals`.
|
|
|
75
73
|
## Resources
|
|
76
74
|
|
|
77
75
|
- [Roadmap](./ROADMAP.md)
|
|
78
|
-
- [Contributing
|
|
76
|
+
- [Contributing](./CONTRIBUTING.md)
|
|
79
77
|
- [License](./LICENSE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vollowx/seele",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.3",
|
|
4
4
|
"description": "Standard Extensible Elements. A web components library that can be styled and extended freely, pre-providing components in Material Design 3.",
|
|
5
5
|
"author": "vollowx",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"module": "./src/all.js",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": "./src/all.js",
|
|
12
|
-
"./
|
|
13
|
-
"./
|
|
12
|
+
"./base/*": "./src/base/*",
|
|
13
|
+
"./m3/*": "./src/m3/*"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"src/**/*.js",
|
|
@@ -49,20 +49,20 @@
|
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@custom-elements-manifest/analyzer": "^0.11.0",
|
|
52
|
-
"@swc/core": "^1.15.
|
|
53
|
-
"@types/bun": "^1.3.
|
|
52
|
+
"@swc/core": "^1.15.18",
|
|
53
|
+
"@types/bun": "^1.3.11",
|
|
54
54
|
"@web/dev-server": "^0.4.6",
|
|
55
|
-
"@web/dev-server-esbuild": "^1.0.
|
|
55
|
+
"@web/dev-server-esbuild": "^1.0.5",
|
|
56
56
|
"chokidar": "^5.0.0",
|
|
57
|
-
"lightningcss": "^1.
|
|
58
|
-
"postcss": "^8.5.
|
|
57
|
+
"lightningcss": "^1.32.0",
|
|
58
|
+
"postcss": "^8.5.8",
|
|
59
59
|
"postcss-cli": "^11.0.1",
|
|
60
60
|
"postcss-sorting": "^9.1.0",
|
|
61
61
|
"prettier": "^3.8.1"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@floating-ui/dom": "^1.7.
|
|
65
|
-
"@swc/helpers": "^0.5.
|
|
64
|
+
"@floating-ui/dom": "^1.7.6",
|
|
65
|
+
"@swc/helpers": "^0.5.19",
|
|
66
66
|
"lit": "^3.3.2"
|
|
67
67
|
}
|
|
68
68
|
}
|
package/src/base/autocomplete.js
CHANGED
|
@@ -25,9 +25,11 @@ const Base = FocusDelegated(InternalsAttached(LitElement));
|
|
|
25
25
|
* id="menu"
|
|
26
26
|
* type="listbox"
|
|
27
27
|
* data-tabindex="-1"
|
|
28
|
+
* ?quick="${this.quick}"
|
|
28
29
|
* .offset=${this.offset}
|
|
29
30
|
* .align=${this.align}
|
|
30
31
|
* .alignStrategy=${this.alignStrategy}
|
|
32
|
+
* ?keep-open-select=${this.keepOpenSelect}
|
|
31
33
|
* no-focus-control
|
|
32
34
|
* ?open=${this.open}
|
|
33
35
|
* @open="${() => (this.open = true)}"
|
|
@@ -108,7 +110,7 @@ const Base = FocusDelegated(InternalsAttached(LitElement));
|
|
|
108
110
|
eventClone.preventDefault = ()=>event.preventDefault();
|
|
109
111
|
eventClone.stopPropagation = ()=>event.stopPropagation();
|
|
110
112
|
this.$menu.$menu.dispatchEvent(eventClone);
|
|
111
|
-
if (event.key === 'Enter') this.open = false;
|
|
113
|
+
if (event.key === 'Enter' && !this.keepOpenSelect) this.open = false;
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
116
|
handleMenuSelect(event) {
|
|
@@ -117,7 +119,9 @@ const Base = FocusDelegated(InternalsAttached(LitElement));
|
|
|
117
119
|
if (this.$input) {
|
|
118
120
|
this.$input.value = newValue;
|
|
119
121
|
}
|
|
120
|
-
this.
|
|
122
|
+
if (!this.keepOpenSelect) {
|
|
123
|
+
this.open = false;
|
|
124
|
+
}
|
|
121
125
|
}
|
|
122
126
|
updated(changed) {
|
|
123
127
|
if (changed.has('open') && this.$input) {
|
|
@@ -128,7 +132,8 @@ const Base = FocusDelegated(InternalsAttached(LitElement));
|
|
|
128
132
|
}
|
|
129
133
|
}
|
|
130
134
|
constructor(...args){
|
|
131
|
-
super(...args), this.open = false,
|
|
135
|
+
super(...args), this.open = false, // Passed to menu
|
|
136
|
+
this.quick = false, this.offset = 0, this.align = 'bottom-start', this.alignStrategy = 'absolute', this.keepOpenSelect = false, this.mode = 'none';
|
|
132
137
|
}
|
|
133
138
|
}
|
|
134
139
|
_ts_decorate([
|
|
@@ -136,6 +141,11 @@ _ts_decorate([
|
|
|
136
141
|
type: Boolean
|
|
137
142
|
})
|
|
138
143
|
], Autocomplete.prototype, "open", void 0);
|
|
144
|
+
_ts_decorate([
|
|
145
|
+
property({
|
|
146
|
+
type: Boolean
|
|
147
|
+
})
|
|
148
|
+
], Autocomplete.prototype, "quick", void 0);
|
|
139
149
|
_ts_decorate([
|
|
140
150
|
property({
|
|
141
151
|
type: Number
|
|
@@ -153,6 +163,12 @@ _ts_decorate([
|
|
|
153
163
|
attribute: 'align-strategy'
|
|
154
164
|
})
|
|
155
165
|
], Autocomplete.prototype, "alignStrategy", void 0);
|
|
166
|
+
_ts_decorate([
|
|
167
|
+
property({
|
|
168
|
+
type: Boolean,
|
|
169
|
+
attribute: 'keep-open-select'
|
|
170
|
+
})
|
|
171
|
+
], Autocomplete.prototype, "keepOpenSelect", void 0);
|
|
156
172
|
_ts_decorate([
|
|
157
173
|
property()
|
|
158
174
|
], Autocomplete.prototype, "mode", void 0);
|
package/src/base/list.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
|
|
2
|
+
import { LitElement, html } from 'lit';
|
|
3
|
+
import { property, query, queryAssignedElements } from 'lit/decorators.js';
|
|
4
|
+
import { setFocusVisible } from '../core/focus-visible.js';
|
|
5
|
+
import { InternalsAttached } from './mixins/internals-attached.js';
|
|
6
|
+
import { FocusDelegated } from './mixins/focus-delegated.js';
|
|
7
|
+
import { ListController } from './controllers/list-controller.js';
|
|
8
|
+
const Base = FocusDelegated(InternalsAttached(LitElement));
|
|
9
|
+
/**
|
|
10
|
+
* @csspart list
|
|
11
|
+
* @csspart items
|
|
12
|
+
*
|
|
13
|
+
* @fires {Event} open - Fires when the menu is opened.
|
|
14
|
+
* @fires {Event} close - Fires when the menu is closed.
|
|
15
|
+
* @fires {ListSelectEvent} select - Fires when an item is selected.
|
|
16
|
+
* @fires {ListItemFocusEvent} item-focus - Fires when an item is focused
|
|
17
|
+
*/ export class List extends Base {
|
|
18
|
+
get $items() {
|
|
19
|
+
return this.listController.items || [];
|
|
20
|
+
}
|
|
21
|
+
render() {
|
|
22
|
+
return html`<div
|
|
23
|
+
part="list"
|
|
24
|
+
role="listbox"
|
|
25
|
+
tabindex="0"
|
|
26
|
+
@keydown=${this.#handleKeyDown}
|
|
27
|
+
@focusin=${this.#handleFocusIn}
|
|
28
|
+
@focusout=${this.#handleFocusOut}
|
|
29
|
+
@pointerdown=${this.#handlePointerDown}
|
|
30
|
+
@click=${this.#handleClick}
|
|
31
|
+
@mouseover=${this.#handleMouseOver}
|
|
32
|
+
>
|
|
33
|
+
${this.renderItemSlot()}
|
|
34
|
+
</div>`;
|
|
35
|
+
}
|
|
36
|
+
renderItemSlot() {
|
|
37
|
+
return html`<slot part="items"></slot>`;
|
|
38
|
+
}
|
|
39
|
+
updated(changed) {
|
|
40
|
+
// TODO: Find somewhere to put this
|
|
41
|
+
// this.listController.clearSearch();
|
|
42
|
+
}
|
|
43
|
+
#handleKeyDown(event) {
|
|
44
|
+
if (event.defaultPrevented) return;
|
|
45
|
+
const action = getActionFromKey(event);
|
|
46
|
+
const items = this.$items;
|
|
47
|
+
const currentIndex = this.listController.currentIndex;
|
|
48
|
+
const maxIndex = items.length - 1;
|
|
49
|
+
switch(action){
|
|
50
|
+
case MenuActions.Last:
|
|
51
|
+
case MenuActions.First:
|
|
52
|
+
case MenuActions.Next:
|
|
53
|
+
case MenuActions.Previous:
|
|
54
|
+
case MenuActions.PageUp:
|
|
55
|
+
case MenuActions.PageDown:
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
const nextIndex = getUpdatedIndex(currentIndex, maxIndex, action);
|
|
58
|
+
this.listController._focusItem(items[nextIndex]);
|
|
59
|
+
return;
|
|
60
|
+
case MenuActions.CloseSelect:
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
if (currentIndex >= 0) {
|
|
63
|
+
items[currentIndex].focused = false;
|
|
64
|
+
this.dispatchEvent(new CustomEvent('select', {
|
|
65
|
+
detail: {
|
|
66
|
+
item: items[currentIndex],
|
|
67
|
+
index: currentIndex
|
|
68
|
+
},
|
|
69
|
+
bubbles: true,
|
|
70
|
+
composed: true
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
case MenuActions.Type:
|
|
75
|
+
this.listController.handleType(event.key);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
#handleFocusIn() {
|
|
80
|
+
if (this.currentIndex == -1) this.listController.focusFirstItem();
|
|
81
|
+
else this.$items[this.currentIndex].focused = true;
|
|
82
|
+
}
|
|
83
|
+
#handleFocusOut() {
|
|
84
|
+
this.$items[this.currentIndex].focused = false;
|
|
85
|
+
}
|
|
86
|
+
#handleMouseOver(event) {
|
|
87
|
+
setFocusVisible(false);
|
|
88
|
+
const item = event.target.closest(this._possibleItemTags.join(','));
|
|
89
|
+
if (item && this.listController.items.includes(item)) {
|
|
90
|
+
this.listController._focusItem(item);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
#handlePointerDown(event) {
|
|
94
|
+
event.preventDefault(); // This makes sure that the container is focused
|
|
95
|
+
this.$list.focus();
|
|
96
|
+
const item = this.#getEventItem(event);
|
|
97
|
+
if (!item || !this.listController.items.includes(item)) return;
|
|
98
|
+
this.listController._focusItem(item);
|
|
99
|
+
}
|
|
100
|
+
#handleClick(event) {
|
|
101
|
+
this.$list.focus();
|
|
102
|
+
const item = this.#getEventItem(event);
|
|
103
|
+
if (!item || !this.listController.items.includes(item)) return;
|
|
104
|
+
this.dispatchEvent(new CustomEvent('select', {
|
|
105
|
+
detail: {
|
|
106
|
+
item: item,
|
|
107
|
+
index: this.listController.items.indexOf(item)
|
|
108
|
+
},
|
|
109
|
+
bubbles: true,
|
|
110
|
+
composed: true
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
113
|
+
#getEventItem(event) {
|
|
114
|
+
const selector = this._possibleItemTags.join(',');
|
|
115
|
+
return event.target.closest(selector);
|
|
116
|
+
}
|
|
117
|
+
get currentIndex() {
|
|
118
|
+
return this.listController?.currentIndex;
|
|
119
|
+
}
|
|
120
|
+
focusFirstItem() {
|
|
121
|
+
this.listController.focusFirstItem();
|
|
122
|
+
}
|
|
123
|
+
focusLastItem() {
|
|
124
|
+
this.listController.focusLastItem();
|
|
125
|
+
}
|
|
126
|
+
focusItem(item) {
|
|
127
|
+
this.listController._focusItem(item);
|
|
128
|
+
}
|
|
129
|
+
constructor(...args){
|
|
130
|
+
super(...args), this._possibleItemTags = [], this._scrollPadding = 0, this.noFocusControl = false, this.listController = new ListController(this, {
|
|
131
|
+
isItem: (item)=>this._possibleItemTags.includes(item.tagName.toLowerCase()) && !item.hasAttribute('disabled') && !item.hidden,
|
|
132
|
+
getPossibleItems: ()=>this.slotItems,
|
|
133
|
+
blurItem: (item)=>{
|
|
134
|
+
item.focused = false;
|
|
135
|
+
},
|
|
136
|
+
focusItem: (item)=>{
|
|
137
|
+
item.focused = true;
|
|
138
|
+
if (!this.noFocusControl) {
|
|
139
|
+
this.$list.ariaActiveDescendantElement = item;
|
|
140
|
+
}
|
|
141
|
+
scrollItemIntoView(this.$list, item, this._scrollPadding);
|
|
142
|
+
this.dispatchEvent(new CustomEvent('item-focus', {
|
|
143
|
+
detail: {
|
|
144
|
+
item: item
|
|
145
|
+
},
|
|
146
|
+
bubbles: true,
|
|
147
|
+
composed: true
|
|
148
|
+
}));
|
|
149
|
+
},
|
|
150
|
+
wrapNavigation: ()=>false
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
_ts_decorate([
|
|
155
|
+
property({
|
|
156
|
+
type: Boolean,
|
|
157
|
+
attribute: 'no-focus-control'
|
|
158
|
+
})
|
|
159
|
+
], List.prototype, "noFocusControl", void 0);
|
|
160
|
+
_ts_decorate([
|
|
161
|
+
query('[part="list"]')
|
|
162
|
+
], List.prototype, "$list", void 0);
|
|
163
|
+
_ts_decorate([
|
|
164
|
+
queryAssignedElements({
|
|
165
|
+
flatten: true
|
|
166
|
+
})
|
|
167
|
+
], List.prototype, "slotItems", void 0);
|
|
168
|
+
// Reference: https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/
|
|
169
|
+
export const MenuActions = {
|
|
170
|
+
Close: 0,
|
|
171
|
+
CloseSelect: 1,
|
|
172
|
+
First: 2,
|
|
173
|
+
Last: 3,
|
|
174
|
+
Next: 4,
|
|
175
|
+
Open: 5,
|
|
176
|
+
PageDown: 6,
|
|
177
|
+
PageUp: 7,
|
|
178
|
+
Previous: 8,
|
|
179
|
+
Select: 9,
|
|
180
|
+
Type: 10
|
|
181
|
+
};
|
|
182
|
+
export function filterOptions(options = [], filter, exclude = []) {
|
|
183
|
+
return options.filter((option)=>{
|
|
184
|
+
const matches = option.toLowerCase().indexOf(filter.toLowerCase()) === 0;
|
|
185
|
+
return matches && exclude.indexOf(option) < 0;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
export function getActionFromKey(event) {
|
|
189
|
+
const { key, altKey, ctrlKey, metaKey } = event;
|
|
190
|
+
if (key === 'Home') {
|
|
191
|
+
return MenuActions.First;
|
|
192
|
+
}
|
|
193
|
+
if (key === 'End') {
|
|
194
|
+
return MenuActions.Last;
|
|
195
|
+
}
|
|
196
|
+
if (key === 'Backspace' || key === 'Clear' || key.length === 1 && key !== ' ' && !altKey && !ctrlKey && !metaKey) {
|
|
197
|
+
return MenuActions.Type;
|
|
198
|
+
}
|
|
199
|
+
if (key === 'ArrowUp' && altKey) {
|
|
200
|
+
return MenuActions.CloseSelect;
|
|
201
|
+
} else if (key === 'ArrowDown' && !altKey) {
|
|
202
|
+
return MenuActions.Next;
|
|
203
|
+
} else if (key === 'ArrowUp') {
|
|
204
|
+
return MenuActions.Previous;
|
|
205
|
+
} else if (key === 'PageUp') {
|
|
206
|
+
return MenuActions.PageUp;
|
|
207
|
+
} else if (key === 'PageDown') {
|
|
208
|
+
return MenuActions.PageDown;
|
|
209
|
+
} else if (key === 'Escape') {
|
|
210
|
+
return MenuActions.Close;
|
|
211
|
+
} else if (key === 'Enter' || key === ' ') {
|
|
212
|
+
return MenuActions.CloseSelect;
|
|
213
|
+
}
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
export function getUpdatedIndex(currentIndex, maxIndex, action) {
|
|
217
|
+
const pageSize = 10;
|
|
218
|
+
switch(action){
|
|
219
|
+
case MenuActions.First:
|
|
220
|
+
return 0;
|
|
221
|
+
case MenuActions.Last:
|
|
222
|
+
return maxIndex;
|
|
223
|
+
case MenuActions.Previous:
|
|
224
|
+
return Math.max(0, currentIndex - 1);
|
|
225
|
+
case MenuActions.Next:
|
|
226
|
+
return Math.min(maxIndex, currentIndex + 1);
|
|
227
|
+
case MenuActions.PageUp:
|
|
228
|
+
return Math.max(0, currentIndex - pageSize);
|
|
229
|
+
case MenuActions.PageDown:
|
|
230
|
+
return Math.min(maxIndex, currentIndex + pageSize);
|
|
231
|
+
default:
|
|
232
|
+
return currentIndex;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
export function scrollItemIntoView(list, item, paddingY = 0) {
|
|
236
|
+
if (!list) return;
|
|
237
|
+
// Basic scroll into view logic
|
|
238
|
+
const menuRect = list.getBoundingClientRect();
|
|
239
|
+
const itemRect = item.getBoundingClientRect();
|
|
240
|
+
if (itemRect.bottom + paddingY > menuRect.bottom) {
|
|
241
|
+
list.scrollTop += itemRect.bottom - menuRect.bottom + paddingY;
|
|
242
|
+
} else if (itemRect.top - paddingY < menuRect.top) {
|
|
243
|
+
list.scrollTop -= menuRect.top - itemRect.top + paddingY;
|
|
244
|
+
}
|
|
245
|
+
}
|
package/src/base/menu.js
CHANGED
|
@@ -123,7 +123,7 @@ const Base = FocusDelegated(InternalsAttached(Attachable(LitElement)));
|
|
|
123
123
|
bubbles: true,
|
|
124
124
|
composed: true
|
|
125
125
|
}));
|
|
126
|
-
if (this.
|
|
126
|
+
if (this.keepOpenSelect) return;
|
|
127
127
|
this.open = false;
|
|
128
128
|
}
|
|
129
129
|
return;
|
|
@@ -166,7 +166,7 @@ const Base = FocusDelegated(InternalsAttached(Attachable(LitElement)));
|
|
|
166
166
|
bubbles: true,
|
|
167
167
|
composed: true
|
|
168
168
|
}));
|
|
169
|
-
if (!this.
|
|
169
|
+
if (!this.keepOpenSelect) this.open = false;
|
|
170
170
|
}
|
|
171
171
|
get currentIndex() {
|
|
172
172
|
return this.listController?.currentIndex;
|
|
@@ -190,7 +190,7 @@ const Base = FocusDelegated(InternalsAttached(Attachable(LitElement)));
|
|
|
190
190
|
super(...args), this._possibleItemTags = [], this._durations = {
|
|
191
191
|
show: 0,
|
|
192
192
|
hide: 0
|
|
193
|
-
}, this._scrollPadding = 0, this.type = 'menu', this.open = false, this.quick = false, this.offset = 0, this.align = 'bottom-start', this.alignStrategy = 'absolute', this.keepOpenBlur = false, this.
|
|
193
|
+
}, this._scrollPadding = 0, this.type = 'menu', this.open = false, this.quick = false, this.offset = 0, this.align = 'bottom-start', this.alignStrategy = 'absolute', this.keepOpenBlur = false, this.keepOpenSelect = false, this.keepOpenClickAway = false, this.noFocusControl = false, this.tabIndex = 0, this.$lastFocused = null, this.popoverController = new PopoverController(this, {
|
|
194
194
|
popover: ()=>this.$menu,
|
|
195
195
|
trigger: ()=>this.$control,
|
|
196
196
|
positioning: {
|
|
@@ -271,9 +271,9 @@ _ts_decorate([
|
|
|
271
271
|
_ts_decorate([
|
|
272
272
|
property({
|
|
273
273
|
type: Boolean,
|
|
274
|
-
attribute: 'keep-open-
|
|
274
|
+
attribute: 'keep-open-select'
|
|
275
275
|
})
|
|
276
|
-
], Menu.prototype, "
|
|
276
|
+
], Menu.prototype, "keepOpenSelect", void 0);
|
|
277
277
|
_ts_decorate([
|
|
278
278
|
property({
|
|
279
279
|
type: Boolean,
|
|
@@ -16,9 +16,11 @@ export class M3Autocomplete extends Autocomplete {
|
|
|
16
16
|
id="menu"
|
|
17
17
|
type="listbox"
|
|
18
18
|
data-tabindex="-1"
|
|
19
|
+
?quick="${this.quick}"
|
|
19
20
|
.offset=${this.offset}
|
|
20
21
|
.align=${this.align}
|
|
21
22
|
.alignStrategy=${this.alignStrategy}
|
|
23
|
+
?keep-open-select=${this.keepOpenSelect}
|
|
22
24
|
no-focus-control
|
|
23
25
|
?open=${this.open}
|
|
24
26
|
@open="${()=>this.open = true}"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { css } from 'lit';
|
|
2
|
-
export const focusRingStyles = css`:host{animation-delay:0s, calc(var(--md-focus-ring-duration,.6s) * .25);animation-duration:calc(var(--md-focus-ring-duration,.6s) * .25), calc(var(--md-focus-ring-duration,.6s) * .75);
|
|
2
|
+
export const focusRingStyles = css`:host{animation-delay:0s, calc(var(--md-focus-ring-duration,.6s) * .25);animation-duration:calc(var(--md-focus-ring-duration,.6s) * .25), calc(var(--md-focus-ring-duration,.6s) * .75);box-sizing:border-box;color:var(--md-focus-ring-color,var(--md-sys-color-secondary));pointer-events:none;animation-timing-function:cubic-bezier(.2,0,0,1),cubic-bezier(.2,0,0,1);display:none;position:absolute}:host(:state(visible)){display:flex}:host(:not([inward])){box-shadow:0 0 0 var(--md-focus-ring-width,3px) currentColor;inset:calc(-1 * var(--md-focus-ring-outward-offset,2px));border-start-start-radius:calc(var(--md-focus-ring-shape-start-start,var(--md-focus-ring-shape,9999px)) + var(--md-focus-ring-outward-offset,2px));border-start-end-radius:calc(var(--md-focus-ring-shape-start-end,var(--md-focus-ring-shape,9999px)) + var(--md-focus-ring-outward-offset,2px));border-end-end-radius:calc(var(--md-focus-ring-shape-end-end,var(--md-focus-ring-shape,9999px)) + var(--md-focus-ring-outward-offset,2px));border-end-start-radius:calc(var(--md-focus-ring-shape-end-start,var(--md-focus-ring-shape,9999px)) + var(--md-focus-ring-outward-offset,2px));animation-name:outward-grow,outward-shrink}:host([inward]){box-shadow:inset 0 0 0 var(--md-focus-ring-width,3px) currentColor;inset:var(--md-focus-ring-inward-offset,0px);border-start-start-radius:calc(var(--md-focus-ring-shape-start-start,var(--md-focus-ring-shape,9999px)) - var(--md-focus-ring-inward-offset,0px));border-start-end-radius:calc(var(--md-focus-ring-shape-start-end,var(--md-focus-ring-shape,9999px)) - var(--md-focus-ring-inward-offset,0px));border-end-end-radius:calc(var(--md-focus-ring-shape-end-end,var(--md-focus-ring-shape,9999px)) - var(--md-focus-ring-inward-offset,0px));border-end-start-radius:calc(var(--md-focus-ring-shape-end-start,var(--md-focus-ring-shape,9999px)) - var(--md-focus-ring-inward-offset,0px));animation-name:inward-grow,inward-shrink}@keyframes outward-grow{0%{box-shadow:0 0}to{box-shadow:0 0 0 var(--md-focus-ring-active-width,8px) currentColor}}@keyframes outward-shrink{0%{box-shadow:0 0 0 var(--md-focus-ring-active-width,8px) currentColor}}@keyframes inward-grow{0%{box-shadow:inset 0 0}to{box-shadow:inset 0 0 0 var(--md-focus-ring-active-width,8px) currentColor}}@keyframes inward-shrink{0%{box-shadow:inset 0 0 0 var(--md-focus-ring-active-width,8px) currentColor}}@media (prefers-reduced-motion){:host{animation:none}}`;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { css } from 'lit';
|
|
2
|
-
export const listStyles = css`:host{box-sizing:border-box;flex-direction:column;display:flex}`;
|
|
2
|
+
export const listStyles = css`:host{min-width:112px;display:contents}[part=list]{box-sizing:border-box;height:inherit;max-height:inherit;max-width:inherit;min-width:inherit;-webkit-user-select:none;user-select:none;width:inherit;outline:0;flex-direction:column;gap:2px;padding:4px;display:flex;position:absolute;inset:auto;overflow-y:auto}`;
|
package/src/m3/list/list.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { _ as _ts_decorate } from "@swc/helpers/_/_ts_decorate";
|
|
2
|
-
import { LitElement } from 'lit';
|
|
3
2
|
import { customElement } from 'lit/decorators.js';
|
|
3
|
+
import { List } from '../../base/list.js';
|
|
4
4
|
import { listStyles } from './list-styles.css.js';
|
|
5
|
-
export class M3List extends
|
|
5
|
+
export class M3List extends List {
|
|
6
6
|
static{
|
|
7
|
+
// FIXME: Might cause a long list to scroll more than expected
|
|
8
|
+
// override readonly _scrollPadding = 4;
|
|
7
9
|
this.styles = [
|
|
8
10
|
listStyles
|
|
9
11
|
];
|
|
10
12
|
}
|
|
13
|
+
constructor(...args){
|
|
14
|
+
super(...args), this._possibleItemTags = [
|
|
15
|
+
'md-list-item',
|
|
16
|
+
'md-list-item-checkbox',
|
|
17
|
+
'md-list-item-radio'
|
|
18
|
+
];
|
|
19
|
+
}
|
|
11
20
|
}
|
|
12
21
|
M3List = _ts_decorate([
|
|
13
22
|
customElement('md-list')
|