@vollowx/seele 0.7.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/LICENSE +21 -0
- package/README.md +79 -0
- package/package.json +62 -0
- package/src/all.js +19 -0
- package/src/base/button.js +61 -0
- package/src/base/checkbox.js +118 -0
- package/src/base/controllers/list-controller.js +96 -0
- package/src/base/controllers/popover-controller.js +163 -0
- package/src/base/field.js +3 -0
- package/src/base/hidden-styles.css.js +2 -0
- package/src/base/input.js +182 -0
- package/src/base/item.js +7 -0
- package/src/base/list-item.js +54 -0
- package/src/base/menu-item.js +12 -0
- package/src/base/menu-utils.js +111 -0
- package/src/base/menu.js +244 -0
- package/src/base/mixins/attachable.js +71 -0
- package/src/base/mixins/form-associated.js +69 -0
- package/src/base/mixins/internals-attached.js +13 -0
- package/src/base/option.js +17 -0
- package/src/base/select.js +285 -0
- package/src/base/switch.js +86 -0
- package/src/base/tooltip.js +139 -0
- package/src/core/focus-visible.js +13 -0
- package/src/core/shared.d.ts +1 -0
- package/src/core/unique-id.js +11 -0
- package/src/m3/button/common-button-styles.css.js +2 -0
- package/src/m3/button/common-button-toggle-styles.css.js +2 -0
- package/src/m3/button/common-button-toggle.js +69 -0
- package/src/m3/button/common-button.js +65 -0
- package/src/m3/button/icon-button-styles.css.js +2 -0
- package/src/m3/button/icon-button-toggle-styles.css.js +2 -0
- package/src/m3/button/icon-button-toggle.js +57 -0
- package/src/m3/button/icon-button.js +51 -0
- package/src/m3/button/shared-button-styles.css.js +2 -0
- package/src/m3/checkbox-styles.css.js +2 -0
- package/src/m3/checkbox.js +46 -0
- package/src/m3/fab-styles.css.js +2 -0
- package/src/m3/fab.js +48 -0
- package/src/m3/field/field-styles.css.js +2 -0
- package/src/m3/field/field.js +93 -0
- package/src/m3/field/filled-field-styles.css.js +2 -0
- package/src/m3/field/filled-field.js +30 -0
- package/src/m3/field/outlined-field-styles.css.js +2 -0
- package/src/m3/field/outlined-field.js +34 -0
- package/src/m3/focus-ring-styles.css.js +2 -0
- package/src/m3/focus-ring.js +72 -0
- package/src/m3/item-styles.css.js +2 -0
- package/src/m3/item.js +46 -0
- package/src/m3/list-item-styles.css.js +2 -0
- package/src/m3/list-item.js +52 -0
- package/src/m3/list-styles.css.js +2 -0
- package/src/m3/list.js +16 -0
- package/src/m3/menu-item.js +15 -0
- package/src/m3/menu-part-styles.css.js +2 -0
- package/src/m3/menu-styles.css.js +2 -0
- package/src/m3/menu.js +30 -0
- package/src/m3/option.js +15 -0
- package/src/m3/ripple-styles.css.js +2 -0
- package/src/m3/ripple.js +199 -0
- package/src/m3/select/filled-select.js +41 -0
- package/src/m3/select/outlined-select.js +41 -0
- package/src/m3/select/select-styles.css.js +2 -0
- package/src/m3/select/select.js +34 -0
- package/src/m3/switch-styles.css.js +2 -0
- package/src/m3/switch.js +129 -0
- package/src/m3/target-styles.css.js +2 -0
- package/src/m3/text-field/filled-text-field.js +38 -0
- package/src/m3/text-field/outlined-text-field.js +40 -0
- package/src/m3/text-field/text-field-styles.css.js +2 -0
- package/src/m3/toolbar-styles.css.js +2 -0
- package/src/m3/toolbar.js +53 -0
- package/src/m3/tooltip-styles.css.js +2 -0
- package/src/m3/tooltip.js +18 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 - 2025 Vollow
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Standard Extensible Elements
|
|
2
|
+
|
|
3
|
+
**SEELE** is a modern, lightweight [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) library. It provides a set of highly customizable UI components that follow the [Material Design 3](https://m3.material.io/) guidelines out of the box, while being designed for easy extension and restyling.
|
|
4
|
+
|
|
5
|
+
Visit the [website of SEELE](https://seele.v9.nz/) for documentation and demos.
|
|
6
|
+
|
|
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
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Install SEELE using your preferred package manager:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# npm
|
|
21
|
+
npm install @vollowx/seele
|
|
22
|
+
|
|
23
|
+
# pnpm
|
|
24
|
+
pnpm add @vollowx/seele
|
|
25
|
+
|
|
26
|
+
# yarn
|
|
27
|
+
yarn add @vollowx/seele
|
|
28
|
+
|
|
29
|
+
# bun
|
|
30
|
+
bun add @vollowx/seele
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Importing Components
|
|
36
|
+
|
|
37
|
+
You can import the entire library or individual components to keep your bundle size small.
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
// Import all components
|
|
41
|
+
import '@vollowx/seele';
|
|
42
|
+
|
|
43
|
+
// OR Import specific components (Recommended)
|
|
44
|
+
import '@vollowx/seele/m3/button/common-button.js';
|
|
45
|
+
import '@vollowx/seele/m3/checkbox.js';
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Using Components
|
|
49
|
+
|
|
50
|
+
Once imported, use the components just like standard HTML tags.
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<md-button variant="filled">Filled Button</md-button>
|
|
54
|
+
<md-button variant="outlined">Outlined Button</md-button>
|
|
55
|
+
|
|
56
|
+
<label>
|
|
57
|
+
<md-checkbox checked></md-checkbox>
|
|
58
|
+
Labelled Checkbox
|
|
59
|
+
</label>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Theming
|
|
63
|
+
|
|
64
|
+
SEELE components use CSS variables for styling. Currently, the global Material Design 3 token variables are not included in the JavaScript bundle.
|
|
65
|
+
|
|
66
|
+
To style the components correctly, you need to define the necessary CSS variables in your project. You can find reference implementations in [vollowx/seele-docs](https://github.com/vollowx/seele-docs/) or the `dev` folder of this repository.
|
|
67
|
+
|
|
68
|
+
## Browser Supporty
|
|
69
|
+
|
|
70
|
+
SEELE relies on modern web standards like `ElementInternals`.
|
|
71
|
+
|
|
72
|
+
- **Chromium**: `>= 125.0`
|
|
73
|
+
- **Firefox**: `>= 126.0`
|
|
74
|
+
|
|
75
|
+
## Resources
|
|
76
|
+
|
|
77
|
+
- [Roadmap](./ROADMAP.md)
|
|
78
|
+
- [Contributing Guide](./CONTRIBUTING.md)
|
|
79
|
+
- [License](./LICENSE)
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vollowx/seele",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Standard Extensible Elements. A web components library that can be styled and extended freely, pre-providing components in Material Design 3.",
|
|
5
|
+
"author": "vollowx",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./src/all.js",
|
|
9
|
+
"module": "./src/all.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/all.js",
|
|
12
|
+
"./m3/*": "./src/m3/*.js",
|
|
13
|
+
"./base/*": "./src/base/*.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src/**/*.js",
|
|
17
|
+
"src/**/*.d.ts",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"web-components",
|
|
23
|
+
"material-design",
|
|
24
|
+
"material-3",
|
|
25
|
+
"lit",
|
|
26
|
+
"ui-components"
|
|
27
|
+
],
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/vollowx/seele.git"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"dev": "wds",
|
|
34
|
+
"dev:styles": "node scripts/convert-styles.mjs watch",
|
|
35
|
+
"build": "bun run build:styles && bun run build:components",
|
|
36
|
+
"build:styles": "node scripts/convert-styles.mjs build",
|
|
37
|
+
"build:components": "tsc",
|
|
38
|
+
"cleanup": "node scripts/cleanup.mjs",
|
|
39
|
+
"prepublishOnly": "bun cleanup && bun run build",
|
|
40
|
+
"format": "prettier . --check",
|
|
41
|
+
"format:fix": "prettier . --check --write",
|
|
42
|
+
"format:sortcss": "bunx postcss \"src/**/*.css\" --replace --no-map"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@custom-elements-manifest/analyzer": "^0.11.0",
|
|
46
|
+
"@types/node": "^25.0.3",
|
|
47
|
+
"@web/dev-server": "^0.4.6",
|
|
48
|
+
"@web/dev-server-esbuild": "^1.0.4",
|
|
49
|
+
"chokidar": "^5.0.0",
|
|
50
|
+
"lightningcss": "^1.30.2",
|
|
51
|
+
"postcss": "^8.5.6",
|
|
52
|
+
"postcss-cli": "^11.0.1",
|
|
53
|
+
"postcss-sorting": "^9.1.0",
|
|
54
|
+
"prettier": "^3.7.4",
|
|
55
|
+
"typescript": "^5.9.3"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@floating-ui/dom": "^1.7.4",
|
|
59
|
+
"lit": "^3.3.2",
|
|
60
|
+
"tslib": "^2.8.1"
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/all.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { M3Button } from './m3/button/common-button.js';
|
|
2
|
+
export { M3ButtonToggle } from './m3/button/common-button-toggle.js';
|
|
3
|
+
export { M3IconButton } from './m3/button/icon-button.js';
|
|
4
|
+
export { M3IconButtonToggle } from './m3/button/icon-button-toggle.js';
|
|
5
|
+
export { M3FilledTextField } from './m3/text-field/filled-text-field.js';
|
|
6
|
+
export { M3OutlinedTextField } from './m3/text-field/outlined-text-field.js';
|
|
7
|
+
export { MdFilledSelect } from './m3/select/filled-select.js';
|
|
8
|
+
export { MdOutlinedSelect } from './m3/select/outlined-select.js';
|
|
9
|
+
export { M3Checkbox } from './m3/checkbox.js';
|
|
10
|
+
export { M3FAB } from './m3/fab.js';
|
|
11
|
+
export { M3Item } from './m3/item.js';
|
|
12
|
+
export { M3List } from './m3/list.js';
|
|
13
|
+
export { M3Menu } from './m3/menu.js';
|
|
14
|
+
export { M3MenuItem } from './m3/menu-item.js';
|
|
15
|
+
export { M3Option } from './m3/option.js';
|
|
16
|
+
export { M3Ripple } from './m3/ripple.js';
|
|
17
|
+
export { M3Switch } from './m3/switch.js';
|
|
18
|
+
export { M3Tooltip } from './m3/tooltip.js';
|
|
19
|
+
export { M3Toolbar } from './m3/toolbar.js';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { LitElement } from 'lit';
|
|
3
|
+
import { property } from 'lit/decorators.js';
|
|
4
|
+
import { InternalsAttached, internals } from './mixins/internals-attached.js';
|
|
5
|
+
import { FormAssociated } from './mixins/form-associated.js';
|
|
6
|
+
import { hiddenStyles } from './hidden-styles.css.js';
|
|
7
|
+
const Base = FormAssociated(InternalsAttached(LitElement));
|
|
8
|
+
export class Button extends Base {
|
|
9
|
+
static { this.styles = [hiddenStyles]; }
|
|
10
|
+
constructor() {
|
|
11
|
+
super();
|
|
12
|
+
this.type = 'button';
|
|
13
|
+
this.#handleKeyDown = (e) => {
|
|
14
|
+
if (e.key !== ' ' && e.key !== 'Enter')
|
|
15
|
+
return;
|
|
16
|
+
e.preventDefault();
|
|
17
|
+
e.stopPropagation();
|
|
18
|
+
if (e.key === 'Enter')
|
|
19
|
+
this.click();
|
|
20
|
+
};
|
|
21
|
+
this.#handleKeyUp = (e) => {
|
|
22
|
+
if (e.key === ' ') {
|
|
23
|
+
e.preventDefault();
|
|
24
|
+
e.stopPropagation();
|
|
25
|
+
this.click();
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
this.#handleClick = () => {
|
|
29
|
+
if (this.type !== 'button')
|
|
30
|
+
this[internals].form?.[this.type]();
|
|
31
|
+
};
|
|
32
|
+
this[internals].role = 'button';
|
|
33
|
+
this.updateState();
|
|
34
|
+
}
|
|
35
|
+
connectedCallback() {
|
|
36
|
+
super.connectedCallback();
|
|
37
|
+
this.addEventListener('keydown', this.#handleKeyDown);
|
|
38
|
+
this.addEventListener('keyup', this.#handleKeyUp);
|
|
39
|
+
this.addEventListener('click', this.#handleClick);
|
|
40
|
+
}
|
|
41
|
+
disconnectedCallback() {
|
|
42
|
+
super.disconnectedCallback();
|
|
43
|
+
this.removeEventListener('keydown', this.#handleKeyDown);
|
|
44
|
+
this.removeEventListener('keyup', this.#handleKeyUp);
|
|
45
|
+
this.removeEventListener('click', this.#handleClick);
|
|
46
|
+
}
|
|
47
|
+
updated(changed) {
|
|
48
|
+
if (changed.has('disabled'))
|
|
49
|
+
this.updateState();
|
|
50
|
+
}
|
|
51
|
+
updateState() {
|
|
52
|
+
this.tabIndex = this.disabled ? -1 : 0;
|
|
53
|
+
this[internals].ariaDisabled = String(this.disabled);
|
|
54
|
+
}
|
|
55
|
+
#handleKeyDown;
|
|
56
|
+
#handleKeyUp;
|
|
57
|
+
#handleClick;
|
|
58
|
+
}
|
|
59
|
+
__decorate([
|
|
60
|
+
property({ reflect: true })
|
|
61
|
+
], Button.prototype, "type", void 0);
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { LitElement } from 'lit';
|
|
3
|
+
import { property } from 'lit/decorators.js';
|
|
4
|
+
import { FormAssociated } from './mixins/form-associated.js';
|
|
5
|
+
import { InternalsAttached, internals } from './mixins/internals-attached.js';
|
|
6
|
+
import { hiddenStyles } from './hidden-styles.css.js';
|
|
7
|
+
const PROPERTY_FROM_ARIA_CHECKED = {
|
|
8
|
+
true: 'checked',
|
|
9
|
+
false: 'unchecked',
|
|
10
|
+
mixed: 'indeterminate',
|
|
11
|
+
};
|
|
12
|
+
const Base = FormAssociated(InternalsAttached(LitElement));
|
|
13
|
+
export class Checkbox extends Base {
|
|
14
|
+
static { this.styles = [hiddenStyles]; }
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.checked = false;
|
|
18
|
+
this.indeterminate = false;
|
|
19
|
+
this.required = false;
|
|
20
|
+
this.#handleClick = (e) => {
|
|
21
|
+
e.stopPropagation();
|
|
22
|
+
e.preventDefault();
|
|
23
|
+
this.#toggleChecked();
|
|
24
|
+
};
|
|
25
|
+
this.#handleKeyDown = (e) => {
|
|
26
|
+
if (e.key === ' ') {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
e.stopPropagation();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
this.#handleKeyUp = (e) => {
|
|
32
|
+
if (e.key === ' ') {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
e.stopPropagation();
|
|
35
|
+
this.#toggleChecked();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
this[internals].role = 'checkbox';
|
|
39
|
+
this.checked = this.hasAttribute('checked');
|
|
40
|
+
this.indeterminate = this.hasAttribute('indeterminate');
|
|
41
|
+
this.updateState();
|
|
42
|
+
}
|
|
43
|
+
connectedCallback() {
|
|
44
|
+
super.connectedCallback();
|
|
45
|
+
this.addEventListener('click', this.#handleClick);
|
|
46
|
+
this.addEventListener('keydown', this.#handleKeyDown);
|
|
47
|
+
this.addEventListener('keyup', this.#handleKeyUp);
|
|
48
|
+
}
|
|
49
|
+
disconnectedCallback() {
|
|
50
|
+
super.disconnectedCallback();
|
|
51
|
+
this.removeEventListener('click', this.#handleClick);
|
|
52
|
+
this.removeEventListener('keydown', this.#handleKeyDown);
|
|
53
|
+
this.removeEventListener('keyup', this.#handleKeyUp);
|
|
54
|
+
}
|
|
55
|
+
updated(changed) {
|
|
56
|
+
if (changed.has('checked') ||
|
|
57
|
+
changed.has('disabled') ||
|
|
58
|
+
changed.has('indeterminate') ||
|
|
59
|
+
changed.has('required')) {
|
|
60
|
+
this.updateState();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
updateState() {
|
|
64
|
+
const prevAriaChecked = this[internals]
|
|
65
|
+
.ariaChecked;
|
|
66
|
+
this[internals].states.delete('was-unchecked');
|
|
67
|
+
this[internals].states.delete('was-checked');
|
|
68
|
+
this[internals].states.delete('was-indeterminate');
|
|
69
|
+
if (prevAriaChecked && PROPERTY_FROM_ARIA_CHECKED[prevAriaChecked]) {
|
|
70
|
+
this[internals].states.add(`was-${PROPERTY_FROM_ARIA_CHECKED[prevAriaChecked]}`);
|
|
71
|
+
}
|
|
72
|
+
this[internals].ariaChecked = this.indeterminate
|
|
73
|
+
? 'mixed'
|
|
74
|
+
: this.checked
|
|
75
|
+
? 'true'
|
|
76
|
+
: 'false';
|
|
77
|
+
const currentAriaChecked = this[internals]
|
|
78
|
+
.ariaChecked;
|
|
79
|
+
this[internals].states.delete('unchecked');
|
|
80
|
+
this[internals].states.delete('checked');
|
|
81
|
+
this[internals].states.delete('indeterminate');
|
|
82
|
+
this[internals].states.add(`${PROPERTY_FROM_ARIA_CHECKED[currentAriaChecked]}`);
|
|
83
|
+
this.tabIndex = this.disabled ? -1 : 0;
|
|
84
|
+
this[internals].ariaDisabled = String(this.disabled);
|
|
85
|
+
this[internals].setFormValue(this.checked ? 'on' : null);
|
|
86
|
+
if (this.required && !this.checked) {
|
|
87
|
+
this[internals].setValidity({ valueMissing: true },
|
|
88
|
+
// TODO: I18n
|
|
89
|
+
'Please check this box if you want to proceed.');
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
this[internals].setValidity({});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
#handleClick;
|
|
96
|
+
#handleKeyDown;
|
|
97
|
+
#handleKeyUp;
|
|
98
|
+
#toggleChecked() {
|
|
99
|
+
if (this.disabled)
|
|
100
|
+
return;
|
|
101
|
+
this.checked = !this.checked;
|
|
102
|
+
this.indeterminate = false;
|
|
103
|
+
this.dispatchEvent(new CustomEvent('change', {
|
|
104
|
+
bubbles: true,
|
|
105
|
+
composed: true,
|
|
106
|
+
detail: this.checked,
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
__decorate([
|
|
111
|
+
property({ type: Boolean })
|
|
112
|
+
], Checkbox.prototype, "checked", void 0);
|
|
113
|
+
__decorate([
|
|
114
|
+
property({ type: Boolean })
|
|
115
|
+
], Checkbox.prototype, "indeterminate", void 0);
|
|
116
|
+
__decorate([
|
|
117
|
+
property({ type: Boolean, reflect: true })
|
|
118
|
+
], Checkbox.prototype, "required", void 0);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { getIndexByLetter } from '../menu-utils.js';
|
|
2
|
+
export class ListController {
|
|
3
|
+
constructor(host, config) {
|
|
4
|
+
this._focusedIndex = -1;
|
|
5
|
+
this.searchString = '';
|
|
6
|
+
this.searchTimeout = null;
|
|
7
|
+
const { isItem, getPossibleItems, blurItem, focusItem, wrapNavigation } = config;
|
|
8
|
+
(this.host = host).addController(this);
|
|
9
|
+
this.isItem = isItem;
|
|
10
|
+
this.getPossibleItems = getPossibleItems;
|
|
11
|
+
this.blurItem = blurItem;
|
|
12
|
+
this.focusItem = focusItem;
|
|
13
|
+
this.wrapNavigation = wrapNavigation;
|
|
14
|
+
}
|
|
15
|
+
hostConnected() { }
|
|
16
|
+
hostDisconnected() { }
|
|
17
|
+
get items() {
|
|
18
|
+
return this.getPossibleItems().filter(this.isItem);
|
|
19
|
+
}
|
|
20
|
+
get currentIndex() {
|
|
21
|
+
const items = this.getPossibleItems().filter(this.isItem);
|
|
22
|
+
return items.findIndex((item) => item.focused) ?? -1;
|
|
23
|
+
}
|
|
24
|
+
handleType(char) {
|
|
25
|
+
const searchString = this.getSearchString(char);
|
|
26
|
+
const items = this.items;
|
|
27
|
+
const optionsText = items.map((item) => item.innerText);
|
|
28
|
+
const searchIndex = getIndexByLetter(optionsText, searchString, this.currentIndex + 1);
|
|
29
|
+
if (searchIndex >= 0) {
|
|
30
|
+
this._focusItem(items[searchIndex]);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
if (this.searchTimeout)
|
|
35
|
+
window.clearTimeout(this.searchTimeout);
|
|
36
|
+
this.searchString = '';
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
getSearchString(char) {
|
|
41
|
+
if (this.searchTimeout) {
|
|
42
|
+
window.clearTimeout(this.searchTimeout);
|
|
43
|
+
}
|
|
44
|
+
this.searchTimeout = window.setTimeout(() => {
|
|
45
|
+
this.searchString = '';
|
|
46
|
+
}, 500);
|
|
47
|
+
this.searchString += char;
|
|
48
|
+
return this.searchString;
|
|
49
|
+
}
|
|
50
|
+
clearSearch() {
|
|
51
|
+
this.searchString = '';
|
|
52
|
+
if (this.searchTimeout) {
|
|
53
|
+
window.clearTimeout(this.searchTimeout);
|
|
54
|
+
this.searchTimeout = null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
_focusItem(item) {
|
|
58
|
+
if (this._focusedIndex !== -1)
|
|
59
|
+
this._blurItem(this.items[this._focusedIndex]);
|
|
60
|
+
this.focusItem(item);
|
|
61
|
+
this._focusedIndex = this.items.indexOf(item);
|
|
62
|
+
}
|
|
63
|
+
_blurItem(item) {
|
|
64
|
+
this.blurItem(item);
|
|
65
|
+
this._focusedIndex = -1;
|
|
66
|
+
}
|
|
67
|
+
focusFirstItem() {
|
|
68
|
+
this._focusItem(this.items[0]);
|
|
69
|
+
}
|
|
70
|
+
focusLastItem() {
|
|
71
|
+
this._focusItem(this.items[this.items.length - 1]);
|
|
72
|
+
}
|
|
73
|
+
focusNextItem() {
|
|
74
|
+
const count = this.items.length;
|
|
75
|
+
if (count === 0)
|
|
76
|
+
return;
|
|
77
|
+
let nextIndex = this._focusedIndex + 1;
|
|
78
|
+
if (nextIndex >= count) {
|
|
79
|
+
nextIndex = this.wrapNavigation() ? count - 1 : 0;
|
|
80
|
+
}
|
|
81
|
+
this._focusItem(this.items[nextIndex]);
|
|
82
|
+
}
|
|
83
|
+
focusPreviousItem() {
|
|
84
|
+
const count = this.items.length;
|
|
85
|
+
if (count === 0)
|
|
86
|
+
return;
|
|
87
|
+
let prevIndex = this._focusedIndex - 1;
|
|
88
|
+
if (prevIndex < 0) {
|
|
89
|
+
prevIndex = this.wrapNavigation() ? 0 : count - 1;
|
|
90
|
+
}
|
|
91
|
+
this._focusItem(this.items[prevIndex]);
|
|
92
|
+
}
|
|
93
|
+
handleSlotChange() {
|
|
94
|
+
this._focusedIndex = this.currentIndex;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { internals, } from '../mixins/internals-attached.js';
|
|
2
|
+
import { autoUpdate, computePosition, arrow, flip, offset, shift, } from '@floating-ui/dom';
|
|
3
|
+
export function transformOriginFromArrow(placement, arrowData) {
|
|
4
|
+
const { x: arrowX, y: arrowY } = arrowData || {};
|
|
5
|
+
const [side] = placement.split('-');
|
|
6
|
+
let originX = '';
|
|
7
|
+
let originY = '';
|
|
8
|
+
if (side === 'top') {
|
|
9
|
+
originX = arrowX != null ? `${arrowX}px` : 'center';
|
|
10
|
+
originY = 'bottom';
|
|
11
|
+
}
|
|
12
|
+
else if (side === 'bottom') {
|
|
13
|
+
originX = arrowX != null ? `${arrowX}px` : 'center';
|
|
14
|
+
originY = 'top';
|
|
15
|
+
}
|
|
16
|
+
else if (side === 'left') {
|
|
17
|
+
originX = 'right';
|
|
18
|
+
originY = arrowY != null ? `${arrowY}px` : 'center';
|
|
19
|
+
}
|
|
20
|
+
else if (side === 'right') {
|
|
21
|
+
originX = 'left';
|
|
22
|
+
originY = arrowY != null ? `${arrowY}px` : 'center';
|
|
23
|
+
}
|
|
24
|
+
return `${originX} ${originY}`;
|
|
25
|
+
}
|
|
26
|
+
export class PopoverController {
|
|
27
|
+
get open() {
|
|
28
|
+
return this._open;
|
|
29
|
+
}
|
|
30
|
+
constructor(host, config) {
|
|
31
|
+
this._open = false;
|
|
32
|
+
// Different from those in the Tooltip, these timers are used to manage the
|
|
33
|
+
// animation timing.
|
|
34
|
+
this.#openTimer = null;
|
|
35
|
+
this.#closeTimer = null;
|
|
36
|
+
// TODO: Provide the ability to specify an arrow element.
|
|
37
|
+
this._dummyArrow = document.createElement('div');
|
|
38
|
+
this.#handleClickOutside = (event) => {
|
|
39
|
+
const trigger = this.config.trigger();
|
|
40
|
+
const popover = this.config.popover();
|
|
41
|
+
const path = event.composedPath();
|
|
42
|
+
if (trigger && path.includes(trigger))
|
|
43
|
+
return;
|
|
44
|
+
if (popover && path.includes(popover))
|
|
45
|
+
return;
|
|
46
|
+
this.config.onClickOutside?.();
|
|
47
|
+
};
|
|
48
|
+
(this.host = host).addController(this);
|
|
49
|
+
this.config = config;
|
|
50
|
+
}
|
|
51
|
+
hostConnected() {
|
|
52
|
+
// Initial state
|
|
53
|
+
if (!this._open) {
|
|
54
|
+
this.host[internals].states.add('closed');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
hostDisconnected() {
|
|
58
|
+
this.cleanupAutoUpdate?.();
|
|
59
|
+
window.removeEventListener('pointerup', this.#handleClickOutside);
|
|
60
|
+
}
|
|
61
|
+
// Different from those in the Tooltip, these timers are used to manage the
|
|
62
|
+
// animation timing.
|
|
63
|
+
#openTimer;
|
|
64
|
+
#closeTimer;
|
|
65
|
+
async animateOpen() {
|
|
66
|
+
if (this._open)
|
|
67
|
+
return;
|
|
68
|
+
this._open = true;
|
|
69
|
+
// Prevent the click that triggered the open from immediately closing it
|
|
70
|
+
// TODO: Use a global event listener to manage this more effectively
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
if (this._open) {
|
|
73
|
+
window.addEventListener('pointerup', this.#handleClickOutside);
|
|
74
|
+
}
|
|
75
|
+
}, 0);
|
|
76
|
+
clearTimeout(this.#openTimer);
|
|
77
|
+
clearTimeout(this.#closeTimer);
|
|
78
|
+
const openDuration = this.config.durations.open();
|
|
79
|
+
this.host[internals].states.delete('closed');
|
|
80
|
+
this.host[internals].states.delete('closing');
|
|
81
|
+
const trigger = this.config.trigger();
|
|
82
|
+
const popover = this.config.popover();
|
|
83
|
+
if (trigger && popover) {
|
|
84
|
+
this.cleanupAutoUpdate?.();
|
|
85
|
+
this.cleanupAutoUpdate = autoUpdate(trigger, popover, () => this.reposition());
|
|
86
|
+
// Initial position
|
|
87
|
+
await this.reposition();
|
|
88
|
+
}
|
|
89
|
+
if (openDuration === 0) {
|
|
90
|
+
this.host[internals].states.add('opened');
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// Wait for a frame to ensure the element is rendered with base styles
|
|
94
|
+
// e.g. (opacity 0) before adding the opening state e.g. (opacity 1) to
|
|
95
|
+
// trigger transition.
|
|
96
|
+
requestAnimationFrame(() => {
|
|
97
|
+
if (this._open) {
|
|
98
|
+
this.host[internals].states.add('opening');
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
this.#openTimer = setTimeout(() => {
|
|
102
|
+
this.host[internals].states.delete('opening');
|
|
103
|
+
this.host[internals].states.add('opened');
|
|
104
|
+
}, openDuration);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async animateClose() {
|
|
108
|
+
if (!this._open)
|
|
109
|
+
return;
|
|
110
|
+
this._open = false;
|
|
111
|
+
window.removeEventListener('pointerup', this.#handleClickOutside);
|
|
112
|
+
clearTimeout(this.#openTimer);
|
|
113
|
+
clearTimeout(this.#closeTimer);
|
|
114
|
+
const closeDuration = this.config.durations.close();
|
|
115
|
+
const wasOpened = this.host[internals].states.has('opened');
|
|
116
|
+
this.host[internals].states.delete('opened');
|
|
117
|
+
this.host[internals].states.delete('opening');
|
|
118
|
+
if (closeDuration === 0 || !wasOpened) {
|
|
119
|
+
this.cleanup();
|
|
120
|
+
this.host[internals].states.delete('closing');
|
|
121
|
+
this.host[internals].states.add('closed');
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
this.host[internals].states.add('closing');
|
|
125
|
+
this.#closeTimer = setTimeout(() => {
|
|
126
|
+
this.cleanup();
|
|
127
|
+
this.host[internals].states.delete('closing');
|
|
128
|
+
this.host[internals].states.add('closed');
|
|
129
|
+
}, closeDuration);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
cleanup() {
|
|
133
|
+
this.cleanupAutoUpdate?.();
|
|
134
|
+
this.cleanupAutoUpdate = undefined;
|
|
135
|
+
}
|
|
136
|
+
reposition() {
|
|
137
|
+
const trigger = this.config.trigger();
|
|
138
|
+
const popover = this.config.popover();
|
|
139
|
+
if (!trigger || !popover)
|
|
140
|
+
return Promise.resolve();
|
|
141
|
+
return computePosition(trigger, popover, {
|
|
142
|
+
placement: this.config.positioning.placement(),
|
|
143
|
+
strategy: this.config.positioning.strategy(),
|
|
144
|
+
middleware: [
|
|
145
|
+
offset(this.config.positioning.offset()),
|
|
146
|
+
flip({ padding: this.config.positioning.windowPadding() }),
|
|
147
|
+
shift({
|
|
148
|
+
padding: this.config.positioning.windowPadding(),
|
|
149
|
+
crossAxis: true,
|
|
150
|
+
}),
|
|
151
|
+
arrow({ element: this._dummyArrow }),
|
|
152
|
+
],
|
|
153
|
+
}).then(({ x, y, strategy, placement, middlewareData }) => {
|
|
154
|
+
Object.assign(popover.style, {
|
|
155
|
+
left: `${x}px`,
|
|
156
|
+
top: `${y}px`,
|
|
157
|
+
position: strategy,
|
|
158
|
+
transformOrigin: transformOriginFromArrow(placement, middlewareData.arrow),
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
#handleClickOutside;
|
|
163
|
+
}
|