@vollowx/seele 0.13.8 → 0.13.10
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 +3 -4
- package/package.json +2 -2
- package/src/all.d.ts +3 -0
- package/src/all.js +3 -0
- package/src/base/autocomplete.js +13 -13
- package/src/base/button.d.ts +1 -1
- package/src/base/button.js +1 -2
- package/src/base/experimental/popup.d.ts +3 -1
- package/src/base/experimental/popup.js +4 -2
- package/src/base/mixins/attachable.d.ts +5 -2
- package/src/base/mixins/attachable.js +63 -23
- package/src/base/mixins/button-toggle-mixin.js +1 -2
- package/src/base/mixins/internals-attached.d.ts +2 -0
- package/src/base/mixins/internals-attached.js +1 -0
- package/src/base/radio.d.ts +3 -0
- package/src/base/radio.js +28 -16
- package/src/base/tab-panel.d.ts +3 -3
- package/src/base/tab-panel.js +9 -11
- package/src/base/tab.d.ts +2 -8
- package/src/base/tab.js +16 -35
- package/src/base/tabs.d.ts +6 -6
- package/src/base/tabs.js +101 -99
- package/src/base/tooltip.d.ts +2 -1
- package/src/base/tooltip.js +2 -2
- package/src/core/ensure-ready.d.ts +2 -1
- package/src/core/ensure-ready.js +12 -7
- package/src/m3/button/shared-button-styles.css.js +1 -1
- package/src/m3/button-group/standard-button-group-styles.css.js +1 -1
- package/src/m3/button-group/standard-button-group.d.ts +2 -0
- package/src/m3/button-group/standard-button-group.js +44 -38
- package/src/m3/checkbox/checkbox.d.ts +1 -1
- package/src/m3/checkbox/checkbox.js +2 -5
- package/src/m3/dialog/dialog.d.ts +3 -1
- package/src/m3/dialog/dialog.js +6 -4
- package/src/m3/focus-ring/focus-ring.d.ts +2 -1
- package/src/m3/focus-ring/focus-ring.js +4 -7
- package/src/m3/navigation/navigation-rail-item-styles.css.js +2 -0
- package/src/m3/navigation/navigation-rail-item.d.ts +21 -0
- package/src/m3/navigation/navigation-rail-item.js +44 -0
- package/src/m3/navigation/navigation-rail-styles.css.js +2 -0
- package/src/m3/navigation/navigation-rail.d.ts +12 -0
- package/src/m3/navigation/navigation-rail.js +17 -0
- package/src/m3/radio/radio-styles.css.js +1 -1
- package/src/m3/radio/radio.d.ts +3 -0
- package/src/m3/radio/radio.js +12 -4
- package/src/m3/ripple/ripple.d.ts +2 -1
- package/src/m3/ripple/ripple.js +20 -22
- package/src/m3/{system → styles}/defaults.css +1 -1
- package/src/m3/styles/motion-expressive.css +15 -0
- package/src/m3/styles/motion-standard.css +15 -0
- package/src/m3/styles/typography.css +24 -0
- package/src/m3/switch/switch.d.ts +1 -0
- package/src/m3/switch/switch.js +4 -3
- package/src/m3/tab/secondary-tab-styles.css.js +2 -0
- package/src/m3/tab/secondary-tab.d.ts +5 -0
- package/src/m3/tab/secondary-tab.js +15 -0
- package/src/m3/tab/tab-panel-styles.css.js +1 -1
- package/src/m3/tab/tab-styles.css.js +1 -1
- package/src/m3/tab/tab.d.ts +3 -0
- package/src/m3/tab/tab.js +68 -3
- package/src/m3/tab/tabs-styles.css.js +1 -1
- package/src/m3/tab/tabs.d.ts +1 -7
- package/src/m3/tab/tabs.js +6 -39
- package/src/m3/system/typography.css +0 -34
- /package/src/m3/{system → styles}/base.css +0 -0
- /package/src/m3/{system → styles}/elevation.css +0 -0
- /package/src/m3/{system → styles}/motion.d.ts +0 -0
- /package/src/m3/{system → styles}/motion.js +0 -0
package/src/base/tabs.js
CHANGED
|
@@ -1,133 +1,138 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
|
-
import { LitElement, html } from 'lit';
|
|
3
|
-
import { property
|
|
2
|
+
import { LitElement, html, isServer } from 'lit';
|
|
3
|
+
import { property } from 'lit/decorators.js';
|
|
4
4
|
import { internals } from './mixins/internals-attached.js';
|
|
5
5
|
/**
|
|
6
|
-
* @fires {CustomEvent}
|
|
6
|
+
* @fires {CustomEvent} select
|
|
7
7
|
*/
|
|
8
8
|
export class Tabs extends LitElement {
|
|
9
|
-
constructor() {
|
|
10
|
-
super(...arguments);
|
|
11
|
-
this._role = 'tablist';
|
|
12
|
-
this.switch = 'manual';
|
|
13
|
-
this.selected = '';
|
|
14
|
-
this.#handleSlotChange = (_) => {
|
|
15
|
-
const parent = this.$parent;
|
|
16
|
-
this.$tabs.forEach((tab) => {
|
|
17
|
-
const panel = parent.querySelector(`[seele-base="tab"][value="${tab.value}"]`);
|
|
18
|
-
if (!panel)
|
|
19
|
-
console.error('[seele] cannot find a matching panel for tab ', tab);
|
|
20
|
-
// TODO: ariaLabelledByElements
|
|
21
|
-
tab.setAttribute('aria-controls', panel.id);
|
|
22
|
-
panel.setAttribute('aria-labelledby', tab.id);
|
|
23
|
-
});
|
|
24
|
-
};
|
|
25
|
-
this.#handleKeyDown = (event) => {
|
|
26
|
-
const tabs = this.$tabs;
|
|
27
|
-
if (!tabs.length)
|
|
28
|
-
return;
|
|
29
|
-
const activeElement = this.getRootNode()
|
|
30
|
-
.activeElement;
|
|
31
|
-
const currentIndex = tabs.findIndex((t) => t === activeElement || t.focused);
|
|
32
|
-
let nextIndex = currentIndex !== -1 ? currentIndex : 0;
|
|
33
|
-
let handled = false;
|
|
34
|
-
switch (event.key) {
|
|
35
|
-
case 'ArrowLeft':
|
|
36
|
-
nextIndex = currentIndex <= 0 ? tabs.length - 1 : currentIndex - 1;
|
|
37
|
-
handled = true;
|
|
38
|
-
break;
|
|
39
|
-
case 'ArrowRight':
|
|
40
|
-
nextIndex = currentIndex >= tabs.length - 1 ? 0 : currentIndex + 1;
|
|
41
|
-
handled = true;
|
|
42
|
-
break;
|
|
43
|
-
case 'Home':
|
|
44
|
-
nextIndex = 0;
|
|
45
|
-
handled = true;
|
|
46
|
-
break;
|
|
47
|
-
case 'End':
|
|
48
|
-
nextIndex = tabs.length - 1;
|
|
49
|
-
handled = true;
|
|
50
|
-
break;
|
|
51
|
-
case 'Enter':
|
|
52
|
-
case ' ':
|
|
53
|
-
if (currentIndex !== -1) {
|
|
54
|
-
this.selectTab(tabs[currentIndex]);
|
|
55
|
-
}
|
|
56
|
-
handled = true;
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
if (handled) {
|
|
60
|
-
event.preventDefault();
|
|
61
|
-
event.stopPropagation();
|
|
62
|
-
if (event.key !== 'Enter' && event.key !== ' ') {
|
|
63
|
-
this.focusTab(tabs[nextIndex]);
|
|
64
|
-
if (this.switch === 'auto')
|
|
65
|
-
this.selectTab(tabs[nextIndex]);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
this.#handleClick = (event) => {
|
|
70
|
-
const target = event.target;
|
|
71
|
-
const tab = target.closest('md-tab');
|
|
72
|
-
if (tab && this.$tabs.includes(tab)) {
|
|
73
|
-
this.focusTab(tab);
|
|
74
|
-
this.selectTab(tab);
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
9
|
get $tabs() {
|
|
79
|
-
return this
|
|
10
|
+
return [...this.querySelectorAll('[seele-base=tab]')];
|
|
80
11
|
}
|
|
81
|
-
get $
|
|
82
|
-
return
|
|
83
|
-
|
|
84
|
-
|
|
12
|
+
get $panels() {
|
|
13
|
+
return [
|
|
14
|
+
...this.querySelectorAll('[seele-base=tabpanel]'),
|
|
15
|
+
];
|
|
85
16
|
}
|
|
86
17
|
render() {
|
|
87
|
-
return html `<div part="tablist" role="tablist"><slot @slotchange="${this.#handleSlotChange}"></slot></div>`;
|
|
18
|
+
return html `<div part="tablist" role="tablist"><slot @slotchange="${this.#handleSlotChange}"></slot></div><slot name="panels"></slot>`;
|
|
19
|
+
}
|
|
20
|
+
constructor() {
|
|
21
|
+
super();
|
|
22
|
+
this.switch = 'manual';
|
|
23
|
+
this.selected = '';
|
|
24
|
+
if (!isServer) {
|
|
25
|
+
this.addEventListener('keydown', this.#handleKeyDown.bind(this));
|
|
26
|
+
this.addEventListener('click', this.#handleClick.bind(this));
|
|
27
|
+
}
|
|
88
28
|
}
|
|
89
29
|
connectedCallback() {
|
|
90
30
|
super.connectedCallback();
|
|
91
|
-
|
|
92
|
-
this.addEventListener('click', this.#handleClick.bind(this));
|
|
31
|
+
queueMicrotask(this.#handleSlotChange.bind(this));
|
|
93
32
|
}
|
|
94
33
|
updated(changed) {
|
|
95
34
|
if (changed.has('selected') && this.selected) {
|
|
96
35
|
const targetTab = this.$tabs.find((t) => t.value === this.selected);
|
|
97
|
-
if (targetTab
|
|
98
|
-
this.
|
|
36
|
+
if (targetTab)
|
|
37
|
+
this._selectTab(targetTab);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
#handleSlotChange() {
|
|
41
|
+
this.$tabs.forEach((tab) => {
|
|
42
|
+
const panel = this.$panels.find((panel) => panel.value === tab.value);
|
|
43
|
+
if (!panel) {
|
|
44
|
+
console.error('[seele] Cannot find a matching panel for tab ', tab);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
tab[internals].ariaControlsElements = [panel];
|
|
48
|
+
panel[internals].ariaLabelledByElements = [tab];
|
|
99
49
|
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
#handleKeyDown(event) {
|
|
53
|
+
const tabs = this.$tabs.filter((t) => !t.disabled);
|
|
54
|
+
if (!tabs.length)
|
|
55
|
+
return;
|
|
56
|
+
const activeElement = this.getRootNode()
|
|
57
|
+
.activeElement;
|
|
58
|
+
const currentIndex = tabs.findIndex((t) => t === activeElement);
|
|
59
|
+
let nextIndex = currentIndex !== -1 ? currentIndex : 0;
|
|
60
|
+
let handled = false;
|
|
61
|
+
switch (event.key) {
|
|
62
|
+
case 'ArrowLeft':
|
|
63
|
+
nextIndex = currentIndex <= 0 ? tabs.length - 1 : currentIndex - 1;
|
|
64
|
+
handled = true;
|
|
65
|
+
break;
|
|
66
|
+
case 'ArrowRight':
|
|
67
|
+
nextIndex = currentIndex >= tabs.length - 1 ? 0 : currentIndex + 1;
|
|
68
|
+
handled = true;
|
|
69
|
+
break;
|
|
70
|
+
case 'Home':
|
|
71
|
+
nextIndex = 0;
|
|
72
|
+
handled = true;
|
|
73
|
+
break;
|
|
74
|
+
case 'End':
|
|
75
|
+
nextIndex = tabs.length - 1;
|
|
76
|
+
handled = true;
|
|
77
|
+
break;
|
|
78
|
+
case 'Enter':
|
|
79
|
+
case ' ':
|
|
80
|
+
if (currentIndex !== -1) {
|
|
81
|
+
this._selectTab(tabs[currentIndex]);
|
|
82
|
+
}
|
|
83
|
+
handled = true;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
if (handled) {
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
event.stopPropagation();
|
|
89
|
+
if (event.key !== 'Enter' && event.key !== ' ') {
|
|
90
|
+
this.focusTab(tabs[nextIndex]);
|
|
91
|
+
if (this.switch === 'auto')
|
|
92
|
+
this._selectTab(tabs[nextIndex], true);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
#handleClick(event) {
|
|
97
|
+
const target = event.target;
|
|
98
|
+
const tab = target.closest('[seele-base=tab]');
|
|
99
|
+
if (tab && !tab.disabled && this.$tabs.includes(tab)) {
|
|
100
|
+
this.focusTab(tab);
|
|
101
|
+
this._selectTab(tab, true);
|
|
100
102
|
}
|
|
101
103
|
}
|
|
102
|
-
#handleSlotChange;
|
|
103
|
-
#handleKeyDown;
|
|
104
|
-
#handleClick;
|
|
105
104
|
focusTab(tab) {
|
|
106
105
|
this.$tabs.forEach((t) => {
|
|
107
106
|
t.blur();
|
|
108
107
|
t.tabIndex = -1;
|
|
109
108
|
});
|
|
110
109
|
tab.tabIndex = 0;
|
|
111
|
-
|
|
110
|
+
// TODO: prevent scrolling in other containers
|
|
111
|
+
tab.scrollIntoView({
|
|
112
|
+
block: 'nearest',
|
|
113
|
+
inline: 'nearest',
|
|
114
|
+
behavior: 'smooth',
|
|
115
|
+
});
|
|
116
|
+
tab.focus({ preventScroll: true });
|
|
112
117
|
}
|
|
113
|
-
|
|
118
|
+
_selectTab(selectedTab, dispatchEvent = false) {
|
|
114
119
|
this.selected = selectedTab.value;
|
|
115
|
-
const
|
|
116
|
-
const panels = Array.from(parent.querySelectorAll('[seele-base="tabpanel"]'));
|
|
120
|
+
const panels = this.$panels;
|
|
117
121
|
this.$tabs.forEach((tab) => {
|
|
118
122
|
tab.selected = tab === selectedTab;
|
|
119
|
-
tab.tabIndex = tab === selectedTab ? 0 : -1;
|
|
123
|
+
tab.tabIndex = tab === selectedTab && !tab.disabled ? 0 : -1;
|
|
120
124
|
});
|
|
121
125
|
panels.forEach((panel) => {
|
|
122
126
|
if (panel.value && selectedTab.value) {
|
|
123
|
-
panel.
|
|
127
|
+
panel.selected = panel.value === selectedTab.value;
|
|
124
128
|
}
|
|
125
129
|
});
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
if (dispatchEvent)
|
|
131
|
+
this.dispatchEvent(new CustomEvent('select', {
|
|
132
|
+
detail: { tab: selectedTab, value: this.selected },
|
|
133
|
+
bubbles: true,
|
|
134
|
+
composed: true,
|
|
135
|
+
}));
|
|
131
136
|
}
|
|
132
137
|
}
|
|
133
138
|
__decorate([
|
|
@@ -136,6 +141,3 @@ __decorate([
|
|
|
136
141
|
__decorate([
|
|
137
142
|
property({ type: String, reflect: true })
|
|
138
143
|
], Tabs.prototype, "selected", void 0);
|
|
139
|
-
__decorate([
|
|
140
|
-
queryAssignedElements({ flatten: true })
|
|
141
|
-
], Tabs.prototype, "$slotItems", void 0);
|
package/src/base/tooltip.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
|
+
import { handleControlChange } from './mixins/attachable.js';
|
|
2
3
|
declare const Base: Constructor<import("./mixins/attachable.js").AttachableInterface> & Constructor<import("./mixins/internals-attached.js").InternalsAttachedInterface> & typeof LitElement;
|
|
3
4
|
/**
|
|
4
5
|
* TODO: Check if $control controls a menu/popover and keep hidden when it is open.
|
|
@@ -31,7 +32,7 @@ export declare class Tooltip extends Base {
|
|
|
31
32
|
render(): import("lit-html").TemplateResult<1>;
|
|
32
33
|
private readonly popoverController;
|
|
33
34
|
constructor();
|
|
34
|
-
handleControlChange(prev?: HTMLElement | null, next?: HTMLElement | null): void;
|
|
35
|
+
[handleControlChange](prev?: HTMLElement | null, next?: HTMLElement | null): void;
|
|
35
36
|
set visible(value: boolean);
|
|
36
37
|
}
|
|
37
38
|
export {};
|
package/src/base/tooltip.js
CHANGED
|
@@ -2,7 +2,7 @@ import { __decorate } from "tslib";
|
|
|
2
2
|
import { LitElement, html } from 'lit';
|
|
3
3
|
import { property, query } from 'lit/decorators.js';
|
|
4
4
|
import { focusVisible } from '../core/focus-visible.js';
|
|
5
|
-
import { Attachable } from './mixins/attachable.js';
|
|
5
|
+
import { Attachable, handleControlChange } from './mixins/attachable.js';
|
|
6
6
|
import { InternalsAttached, internals } from './mixins/internals-attached.js';
|
|
7
7
|
import { PopoverController } from './controllers/popover-controller.js';
|
|
8
8
|
let lastHidingTime = 0;
|
|
@@ -83,7 +83,7 @@ export class Tooltip extends Base {
|
|
|
83
83
|
};
|
|
84
84
|
this[internals].role = 'tooltip';
|
|
85
85
|
}
|
|
86
|
-
handleControlChange(prev = null, next = null) {
|
|
86
|
+
[handleControlChange](prev = null, next = null) {
|
|
87
87
|
const eventHandlers = {
|
|
88
88
|
focusin: this.#handleFocusIn,
|
|
89
89
|
focusout: this.#handleFocusOut,
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export declare function ensureReady(element: HTMLElement): Promise<
|
|
1
|
+
export declare function ensureReady(element: HTMLElement | null, useAnimationFrame?: boolean): Promise<void>;
|
|
2
|
+
export declare function ensureSlottedReady(host: HTMLElement, getSlotted?: () => (HTMLElement | null)[], useAnimationFrame?: boolean): Promise<void>;
|
package/src/core/ensure-ready.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
export async function ensureReady(element) {
|
|
1
|
+
export async function ensureReady(element, useAnimationFrame = false) {
|
|
2
2
|
if (!element)
|
|
3
3
|
return null;
|
|
4
4
|
if (element.matches(':not(:defined)'))
|
|
5
5
|
await customElements.whenDefined(element.tagName.toLowerCase());
|
|
6
|
-
|
|
7
|
-
if ('updateComplete' in element) {
|
|
6
|
+
if ('updateComplete' in element)
|
|
8
7
|
await element.updateComplete;
|
|
9
|
-
|
|
10
|
-
else {
|
|
11
|
-
// Yield to the next frame to allow initial render to finish
|
|
8
|
+
if (useAnimationFrame)
|
|
12
9
|
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
10
|
+
}
|
|
11
|
+
export async function ensureSlottedReady(host, getSlotted, useAnimationFrame = false) {
|
|
12
|
+
await ensureReady(host);
|
|
13
|
+
if (getSlotted) {
|
|
14
|
+
const elements = getSlotted();
|
|
15
|
+
await Promise.all(elements.map((el) => ensureReady(el, false)));
|
|
13
16
|
}
|
|
14
|
-
|
|
17
|
+
// Might be useful when you need to use sth like `getBoundingClientRect()`
|
|
18
|
+
if (useAnimationFrame)
|
|
19
|
+
await new Promise((resolve) => requestAnimationFrame(resolve));
|
|
15
20
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { css } from 'lit';
|
|
2
|
-
export const sharedButtonStyles = css`@layer common-button,icon-button;@layer shared{:host{--_on-color:var(--md-sys-color-on-primary);--_color:var(--md-sys-color-primary);--_h:40px;--_icon-size:20px;--_rs:12px;--_rp:8px;--_text-color:var(--_on-color);--_background-color:var(--_color);box-sizing:border-box;cursor:pointer;height:var(--_h);-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none;vertical-align:top;border-start-start-radius:var(--_inner-left-radius,calc(var(--_h) / 2));border-start-end-radius:var(--_inner-right-radius,calc(var(--_h) / 2));border-end-end-radius:var(--_inner-right-radius,calc(var(--_h) / 2));border-end-start-radius:var(--_inner-left-radius,calc(var(--_h) / 2));outline:0;flex-shrink:0;justify-content:center;align-items:center;display:inline-flex;position:relative}:host([notransition]){transition:none!important}md-ripple,md-focus-ring{position:absolute}:host([size=xs]){--_h:32px}:host([size=m]){--_h:56px;--_icon-size:24px;--_rs:16px;--_rp:12px}:host([size=l]){--_h:96px;--_icon-size:32px;--_rs:28px;--_rp:16px}:host([size=xl]){--_h:136px;--_icon-size:40px;--_rs:28px;--_rp:16px}:host([color=secondary]){--_on-color:var(--md-sys-color-on-secondary);--_color:var(--md-sys-color-secondary)}:host([color=tertiary]){--_on-color:var(--md-sys-color-on-tertiary);--_color:var(--md-sys-color-tertiary)}:host([variant=tonal]){--_background-color:var(--md-sys-color-secondary-container);--_text-color:var(--md-sys-color-on-secondary-container)}:host([variant=outlined]){--_background-color:transparent;--_text-color:var(--md-sys-color-on-surface-variant);--md-focus-ring-offset:3px;border-color:color-mix(in srgb, var(--md-sys-color-outline-variant) 100%, transparent);border-style:solid;border-width:1px}:host([variant=text]){--_text-color:var(--_color);--_background-color:transparent!important}:host([square]){--md-focus-ring-shape:var(--_rs);border-radius:var(--_rs)}@media (forced-colors:active){:host{forced-color-adjust:none;color:buttontext;background:buttonface;border:1px solid buttonborder}}}@layer shared-toggle,override;@layer active{:host(:active),:host(:state(active)){--md-focus-ring-shape:var(--_rp);border-radius:var(--_rp)}}@layer disabled{:host([variant=outlined]:disabled){--_background-color:var(--md-sys-color-outline)}:host(:disabled),:host([variant=outlined]:state(checked):disabled){--_background-color:var(--md-sys-color-on-surface);--_background-opacity:10%;--_text-color:var(--md-sys-color-on-surface);--_text-opacity:38%;border-color:color-mix(in srgb, var(--md-sys-color-on-surface) 10%, transparent);box-shadow:none;pointer-events:none}@media (forced-colors:active){:host(:disabled){color:graytext;border-color:graytext}}}`;
|
|
2
|
+
export const sharedButtonStyles = css`@layer common-button,icon-button;@layer shared{:host{--_on-color:var(--md-sys-color-on-primary);--_color:var(--md-sys-color-primary);--_h:40px;--_icon-size:20px;--_rs:12px;--_rp:8px;--_text-color:var(--_on-color);--_background-color:var(--_color);box-sizing:border-box;cursor:pointer;height:var(--_h);-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none;vertical-align:top;border-start-start-radius:var(--_inner-left-radius,calc(var(--_h) / 2));border-start-end-radius:var(--_inner-right-radius,calc(var(--_h) / 2));border-end-end-radius:var(--_inner-right-radius,calc(var(--_h) / 2));border-end-start-radius:var(--_inner-left-radius,calc(var(--_h) / 2));outline:0;flex-shrink:0;justify-content:center;align-items:center;display:inline-flex;position:relative}:host([notransition]){transition:none!important}md-ripple,md-focus-ring{position:absolute}:host([size=xs]){--_h:32px}:host([size=m]){--_h:56px;--_icon-size:24px;--_rs:16px;--_rp:12px}:host([size=l]){--_h:96px;--_icon-size:32px;--_rs:28px;--_rp:16px}:host([size=xl]){--_h:136px;--_icon-size:40px;--_rs:28px;--_rp:16px}:host([color=secondary]){--_on-color:var(--md-sys-color-on-secondary);--_color:var(--md-sys-color-secondary)}:host([color=tertiary]){--_on-color:var(--md-sys-color-on-tertiary);--_color:var(--md-sys-color-tertiary)}:host([variant=tonal]){--_background-color:var(--md-sys-color-secondary-container);--_text-color:var(--md-sys-color-on-secondary-container)}:host([variant=outlined]){--_background-color:transparent;--_text-color:var(--md-sys-color-on-surface-variant);--md-focus-ring-offset:3px;border-color:color-mix(in srgb, var(--md-sys-color-outline-variant) 100%, transparent);border-style:solid;border-width:1px;& md-ripple{inset:-1px}}:host([variant=text]){--_text-color:var(--_color);--_background-color:transparent!important}:host([square]){--md-focus-ring-shape:var(--_rs);border-radius:var(--_rs)}@media (forced-colors:active){:host{forced-color-adjust:none;color:buttontext;background:buttonface;border:1px solid buttonborder}}}@layer shared-toggle,override;@layer active{:host(:active),:host(:state(active)){--md-focus-ring-shape:var(--_rp);border-radius:var(--_rp)}}@layer disabled{:host([variant=outlined]:disabled){--_background-color:var(--md-sys-color-outline)}:host(:disabled),:host([variant=outlined]:state(checked):disabled){--_background-color:var(--md-sys-color-on-surface);--_background-opacity:10%;--_text-color:var(--md-sys-color-on-surface);--_text-opacity:38%;border-color:color-mix(in srgb, var(--md-sys-color-on-surface) 10%, transparent);box-shadow:none;pointer-events:none}@media (forced-colors:active){:host(:disabled){color:graytext;border-color:graytext}}}`;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { css } from 'lit';
|
|
2
|
-
export const standardButtonGroupStyles = css`:host{gap:8px;display:inline-flex}:host([size=s]){gap:12px}:host([size=
|
|
2
|
+
export const standardButtonGroupStyles = css`:host{gap:8px;display:inline-flex}:host([size=s]){gap:12px}:host([size=xs]){gap:18px}`;
|
|
@@ -10,7 +10,9 @@ export declare class M3StandardButtonGroup extends LitElement {
|
|
|
10
10
|
$buttons: Array<HTMLElement>;
|
|
11
11
|
static styles: any[];
|
|
12
12
|
render(): import("lit-html").TemplateResult<1>;
|
|
13
|
+
constructor();
|
|
13
14
|
connectedCallback(): void;
|
|
15
|
+
firstUpdated(): Promise<void>;
|
|
14
16
|
disconnectedCallback(): void;
|
|
15
17
|
/**
|
|
16
18
|
* Passing no element resets all shape changes
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
|
-
import { LitElement, html } from 'lit';
|
|
2
|
+
import { LitElement, html, isServer } from 'lit';
|
|
3
3
|
import { property, queryAssignedElements } from 'lit/decorators.js';
|
|
4
4
|
import { customElement } from '../../core/decorators.js';
|
|
5
|
-
import {
|
|
5
|
+
import { ensureSlottedReady } from '../../core/ensure-ready.js';
|
|
6
|
+
import { getSpring } from '../styles/motion.js';
|
|
6
7
|
import { standardButtonGroupStyles } from './standard-button-group-styles.css.js';
|
|
7
8
|
const EXPAND_FACTOR = 1.15;
|
|
8
9
|
const shapeAnimation = Symbol('shapeAnimation');
|
|
@@ -12,8 +13,12 @@ const shapeAnimation = Symbol('shapeAnimation');
|
|
|
12
13
|
* @slot - buttons
|
|
13
14
|
*/
|
|
14
15
|
let M3StandardButtonGroup = class M3StandardButtonGroup extends LitElement {
|
|
16
|
+
static { this.styles = [standardButtonGroupStyles]; }
|
|
17
|
+
render() {
|
|
18
|
+
return html `<slot @slotchange="${this.#handleSlotChange}"></slot>`;
|
|
19
|
+
}
|
|
15
20
|
constructor() {
|
|
16
|
-
super(
|
|
21
|
+
super();
|
|
17
22
|
this.size = '';
|
|
18
23
|
this.#activeIndex = -1;
|
|
19
24
|
this.#baseWidths = [];
|
|
@@ -42,20 +47,21 @@ let M3StandardButtonGroup = class M3StandardButtonGroup extends LitElement {
|
|
|
42
47
|
this.updateLayout();
|
|
43
48
|
}
|
|
44
49
|
};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
if (!isServer) {
|
|
51
|
+
this.addEventListener('pointerdown', this.#handlePointerDown);
|
|
52
|
+
this.addEventListener('keydown', this.#handleKeyDown, { capture: true });
|
|
53
|
+
}
|
|
49
54
|
}
|
|
50
55
|
connectedCallback() {
|
|
51
56
|
super.connectedCallback();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
window.addEventListener('
|
|
55
|
-
window.addEventListener('
|
|
56
|
-
window.addEventListener('keyup', this.#reset, { capture: true });
|
|
57
|
+
const signal = (this.#abortController = new AbortController()).signal;
|
|
58
|
+
window.addEventListener('pointerup', this.#reset, { signal });
|
|
59
|
+
window.addEventListener('pointercancel', this.#reset, { signal });
|
|
60
|
+
window.addEventListener('keyup', this.#reset, { capture: true, signal });
|
|
57
61
|
this.#resizeObserver = new ResizeObserver((entries) => {
|
|
58
|
-
|
|
62
|
+
const isAnimating = this.#activeIndex !== -1 ||
|
|
63
|
+
this.$buttons.some((btn) => btn[shapeAnimation]);
|
|
64
|
+
if (isAnimating)
|
|
59
65
|
return; // Skip if animating
|
|
60
66
|
entries.forEach((entry) => {
|
|
61
67
|
const target = entry.target;
|
|
@@ -63,22 +69,23 @@ let M3StandardButtonGroup = class M3StandardButtonGroup extends LitElement {
|
|
|
63
69
|
if (index !== -1) {
|
|
64
70
|
const currentWidth = target.style.width;
|
|
65
71
|
target.style.width = '';
|
|
66
|
-
this.#baseWidths[index] =
|
|
72
|
+
this.#baseWidths[index] = target.getBoundingClientRect().width;
|
|
67
73
|
if (currentWidth)
|
|
68
74
|
target.style.width = currentWidth;
|
|
69
75
|
}
|
|
70
76
|
});
|
|
71
77
|
});
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
}
|
|
79
|
+
async firstUpdated() {
|
|
80
|
+
await ensureSlottedReady(this, () => this.$buttons, true);
|
|
81
|
+
this.#handleSlotChange();
|
|
74
82
|
}
|
|
75
83
|
disconnectedCallback() {
|
|
76
|
-
|
|
77
|
-
window.removeEventListener('pointercancel', this.#reset);
|
|
78
|
-
window.removeEventListener('keyup', this.#reset, { capture: true });
|
|
84
|
+
this.#abortController.abort();
|
|
79
85
|
this.#resizeObserver.disconnect();
|
|
80
86
|
super.disconnectedCallback();
|
|
81
87
|
}
|
|
88
|
+
#abortController;
|
|
82
89
|
#activeIndex;
|
|
83
90
|
#resizeObserver;
|
|
84
91
|
#baseWidths;
|
|
@@ -93,35 +100,34 @@ let M3StandardButtonGroup = class M3StandardButtonGroup extends LitElement {
|
|
|
93
100
|
const pressedIndex = target ? this.$buttons.indexOf(target) : -1;
|
|
94
101
|
this.#activeIndex = pressedIndex;
|
|
95
102
|
const pressedBaseWidth = pressedIndex !== -1 ? this.#baseWidths[pressedIndex] : 0;
|
|
96
|
-
const expansionDelta = pressedBaseWidth * (EXPAND_FACTOR - 1);
|
|
97
103
|
const isPressedAtEdge = pressedIndex === 0 || pressedIndex === this.$buttons.length - 1;
|
|
104
|
+
const delta = (pressedBaseWidth * (EXPAND_FACTOR - 1)) / (isPressedAtEdge ? 1 : 2);
|
|
105
|
+
const spring = getSpring(this, 'spatial', 'fast');
|
|
98
106
|
this.$buttons.forEach((btn, i) => {
|
|
99
107
|
const currentWidth = btn.getBoundingClientRect().width;
|
|
100
108
|
let targetWidth = this.#baseWidths[i];
|
|
101
109
|
if (pressedIndex !== -1) {
|
|
102
|
-
if (i === pressedIndex)
|
|
110
|
+
if (i === pressedIndex)
|
|
103
111
|
targetWidth = pressedBaseWidth * EXPAND_FACTOR;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (isPressedAtEdge)
|
|
107
|
-
targetWidth = this.#baseWidths[i] - expansionDelta;
|
|
108
|
-
else
|
|
109
|
-
targetWidth = this.#baseWidths[i] - expansionDelta / 2;
|
|
110
|
-
}
|
|
112
|
+
else if (Math.abs(i - pressedIndex) === 1)
|
|
113
|
+
targetWidth -= delta;
|
|
111
114
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
btn[shapeAnimation].cancel();
|
|
115
|
-
btn[shapeAnimation] = btn.animate([{ width: `${currentWidth}px` }, { width: `${targetWidth}px` }], {
|
|
115
|
+
btn[shapeAnimation]?.cancel();
|
|
116
|
+
const anim = (btn[shapeAnimation] = btn.animate([{ width: `${currentWidth}px` }, { width: `${targetWidth}px` }], {
|
|
116
117
|
...spring,
|
|
117
118
|
fill: 'forwards',
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
}));
|
|
120
|
+
anim.finished
|
|
121
|
+
.then(() => {
|
|
122
|
+
if (btn[shapeAnimation] === anim) {
|
|
123
|
+
if (pressedIndex === -1) {
|
|
124
|
+
btn.style.width = '';
|
|
125
|
+
anim.cancel();
|
|
126
|
+
}
|
|
127
|
+
delete btn[shapeAnimation];
|
|
123
128
|
}
|
|
124
|
-
}
|
|
129
|
+
})
|
|
130
|
+
.catch(() => { });
|
|
125
131
|
});
|
|
126
132
|
}
|
|
127
133
|
};
|
|
@@ -16,7 +16,7 @@ export declare class M3Checkbox extends Checkbox {
|
|
|
16
16
|
static styles: any[];
|
|
17
17
|
render(): import("lit-html").TemplateResult<1>;
|
|
18
18
|
renderIcon(): import("lit-html").TemplateResult<1>;
|
|
19
|
-
|
|
19
|
+
firstUpdated(): void;
|
|
20
20
|
}
|
|
21
21
|
declare global {
|
|
22
22
|
interface HTMLElementTagNameMap {
|
|
@@ -26,12 +26,9 @@ let M3Checkbox = class M3Checkbox extends Checkbox {
|
|
|
26
26
|
renderIcon() {
|
|
27
27
|
return html `<svg part="icon" viewBox="0 0 18 18" aria-hidden="true"><rect part="mark mark-short"></rect><rect part="mark mark-long"></rect></svg>`;
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
super.connectedCallback();
|
|
29
|
+
firstUpdated() {
|
|
31
30
|
// SSR'd <md-checkbox> components don't have their labels set up on time
|
|
32
|
-
this.
|
|
33
|
-
this.$ripple.attach(this, true);
|
|
34
|
-
});
|
|
31
|
+
this.$ripple.attach(this, true);
|
|
35
32
|
}
|
|
36
33
|
};
|
|
37
34
|
__decorate([
|
|
@@ -2,6 +2,8 @@ import { Dialog } from '../../base/dialog.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* TODO: test if form actions `cancel` and other things related work - no,
|
|
4
4
|
* event listener needed
|
|
5
|
+
* FIXME: on Firefox, the focus is not autoatically set to the first tabbable
|
|
6
|
+
* element
|
|
5
7
|
*
|
|
6
8
|
* @tag md-dialog
|
|
7
9
|
*
|
|
@@ -35,7 +37,7 @@ export declare class M3Dialog extends Dialog {
|
|
|
35
37
|
$scrim: HTMLDivElement;
|
|
36
38
|
static styles: any[];
|
|
37
39
|
render(): import("lit-html").TemplateResult<1>;
|
|
38
|
-
|
|
40
|
+
firstUpdated(): Promise<void>;
|
|
39
41
|
show(): void;
|
|
40
42
|
close(): void;
|
|
41
43
|
}
|
package/src/m3/dialog/dialog.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
2
|
import { html } from 'lit';
|
|
3
3
|
import { query, state, customElement } from 'lit/decorators.js';
|
|
4
|
+
import { ensureSlottedReady } from '../../core/ensure-ready.js';
|
|
4
5
|
import { Dialog } from '../../base/dialog.js';
|
|
5
6
|
import { dialogStyles } from './dialog-styles.css.js';
|
|
6
7
|
/**
|
|
7
8
|
* TODO: test if form actions `cancel` and other things related work - no,
|
|
8
9
|
* event listener needed
|
|
10
|
+
* FIXME: on Firefox, the focus is not autoatically set to the first tabbable
|
|
11
|
+
* element
|
|
9
12
|
*
|
|
10
13
|
* @tag md-dialog
|
|
11
14
|
*
|
|
@@ -50,10 +53,9 @@ let M3Dialog = class M3Dialog extends Dialog {
|
|
|
50
53
|
render() {
|
|
51
54
|
return html `<dialog part="dialog" @click="${this.#handleClick}" @cancel="${this._handleCancel}"><div part="container"><div part="body"><div part="headline" class="${this.hasIcon ? 'has-icon' : ''}"><slot name="icon" @slotchange="${this.#handleIconSlotChange}"></slot><slot name="headline"></slot></div><div part="content"><slot></slot></div><div class="actions-placeholder"></div></div><div part="actions"><slot name="actions"></slot></div></div></dialog><div part="scrim"></div>`;
|
|
52
55
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// Which is not called on a SSR'd element
|
|
56
|
+
async firstUpdated() {
|
|
57
|
+
await ensureSlottedReady(this);
|
|
58
|
+
this.#handleIconSlotChange();
|
|
57
59
|
}
|
|
58
60
|
#activeAnimations;
|
|
59
61
|
#clearAnimations() {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
|
+
import { handleControlChange } from '../../base/mixins/attachable.js';
|
|
2
3
|
declare const M3FocusRing_base: Constructor<import("../../base/mixins/attachable.js").AttachableInterface> & Constructor<import("../../base/mixins/internals-attached.js").InternalsAttachedInterface> & typeof LitElement;
|
|
3
4
|
/**
|
|
4
5
|
* @tag md-focus-ring
|
|
@@ -22,7 +23,7 @@ export declare class M3FocusRing extends M3FocusRing_base {
|
|
|
22
23
|
constructor();
|
|
23
24
|
visualFocus(): void;
|
|
24
25
|
visualBlur(): void;
|
|
25
|
-
handleControlChange(prev?: HTMLElement | null, next?: HTMLElement | null): void;
|
|
26
|
+
[handleControlChange](prev?: HTMLElement | null, next?: HTMLElement | null): void;
|
|
26
27
|
}
|
|
27
28
|
declare global {
|
|
28
29
|
interface HTMLElementTagNameMap {
|
|
@@ -3,8 +3,8 @@ import { LitElement } from 'lit';
|
|
|
3
3
|
import { property } from 'lit/decorators.js';
|
|
4
4
|
import { customElement } from '../../core/decorators.js';
|
|
5
5
|
import { focusVisible } from '../../core/focus-visible.js';
|
|
6
|
-
import { Attachable } from '../../base/mixins/attachable.js';
|
|
7
|
-
import { InternalsAttached, internals, } from '../../base/mixins/internals-attached.js';
|
|
6
|
+
import { Attachable, handleControlChange } from '../../base/mixins/attachable.js';
|
|
7
|
+
import { InternalsAttached, internals, replaceStates, } from '../../base/mixins/internals-attached.js';
|
|
8
8
|
import { focusRingStyles } from './focus-ring-styles.css.js';
|
|
9
9
|
/**
|
|
10
10
|
* @tag md-focus-ring
|
|
@@ -27,10 +27,7 @@ let M3FocusRing = class M3FocusRing extends Attachable(InternalsAttached(LitElem
|
|
|
27
27
|
super();
|
|
28
28
|
this.inward = false;
|
|
29
29
|
this.#handleFocusIn = () => {
|
|
30
|
-
|
|
31
|
-
this[internals].states.add('visible');
|
|
32
|
-
else
|
|
33
|
-
this[internals].states.delete('visible');
|
|
30
|
+
this[replaceStates](['visible'], [focusVisible ? 'visible' : null]);
|
|
34
31
|
};
|
|
35
32
|
this.#handleFocusOut = () => {
|
|
36
33
|
this[internals].states.delete('visible');
|
|
@@ -49,7 +46,7 @@ let M3FocusRing = class M3FocusRing extends Attachable(InternalsAttached(LitElem
|
|
|
49
46
|
visualBlur() {
|
|
50
47
|
this.#handleFocusOut();
|
|
51
48
|
}
|
|
52
|
-
handleControlChange(prev = null, next = null) {
|
|
49
|
+
[handleControlChange](prev = null, next = null) {
|
|
53
50
|
const eventHandlers = {
|
|
54
51
|
focusin: this.#handleFocusIn,
|
|
55
52
|
focusout: this.#handleFocusOut,
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
export const navigationRailItemStyles = css`:host{color:var(--md-sys-color-on-surface-variant);cursor:pointer;outline:0;grid-template-rows:40px 1fr;justify-content:flex-start;width:max-content;display:grid;position:relative}.pill-root{grid-template:".start icon.label end."32px/20px 16px 24px 0 0fr 16px 20px;place-self:center;display:grid;position:relative}.pill{border-radius:9999px;grid-area:1/start-start/auto/end-end;position:relative}::slotted(*){fill:currentColor;place-self:center;block-size:1em;inline-size:1em;font-size:24px;position:absolute}::slotted([slot=active]){opacity:0}.label-inside{font:var(--md-sys-typography-label-large);display:none}.label-outside{font:var(--md-sys-typography-label-medium);pointer-events:none;text-align:center;word-break:break-word;grid-template-rows:1fr;width:96px;display:block;overflow:hidden}:host([active]){& .pill{color:var(--md-sys-color-on-secondary-container)}& md-ripple{background-color:var(--md-sys-color-secondary-container)}&::slotted([slot=active]){opacity:1}&::slotted(:not([slot=active])){opacity:0}& .label-outside{color:var(--md-sys-color-secondary)}}`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type PropertyValues } from 'lit';
|
|
2
|
+
import { Button } from '../../base/button.js';
|
|
3
|
+
import '../focus-ring/focus-ring.js';
|
|
4
|
+
import '../ripple/ripple.js';
|
|
5
|
+
import { type M3FocusRing } from '../focus-ring/focus-ring.js';
|
|
6
|
+
import { type M3Ripple } from '../ripple/ripple.js';
|
|
7
|
+
export declare class M3NavigationRailItem extends Button {
|
|
8
|
+
label: string;
|
|
9
|
+
active: boolean;
|
|
10
|
+
$focusRing: M3FocusRing;
|
|
11
|
+
$ripple: M3Ripple;
|
|
12
|
+
static styles: any[];
|
|
13
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
14
|
+
updated(changed: PropertyValues): void;
|
|
15
|
+
firstUpdated(): void;
|
|
16
|
+
}
|
|
17
|
+
declare global {
|
|
18
|
+
interface HTMLElementTagNameMap {
|
|
19
|
+
'md-nav-rail-item': M3NavigationRailItem;
|
|
20
|
+
}
|
|
21
|
+
}
|