fix: 本地 patch MDUI 以解决 tabindex = 0 导致的一系列玄学问题

This commit is contained in:
CrescentLeaf
2025-10-04 11:07:03 +08:00
parent af694f6f6c
commit 6e164cbdfb
480 changed files with 94389 additions and 0 deletions

View File

@@ -0,0 +1 @@
export * from './avatar/index.js';

View File

@@ -0,0 +1 @@
export * from './avatar/index.js';

View File

@@ -0,0 +1,51 @@
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import '../icon.js';
import type { CSSResultGroup, TemplateResult } from 'lit';
/**
* @summary 头像组件
*
* ```html
* <mdui-avatar src="https://avatars.githubusercontent.com/u/3030330?s=40&v=4"></mdui-avatar>
* ```
*
* @slot - 自定义头像内容,可以为字母、汉字、`<img>` 元素、图标等
*
* @csspart image - 使用图片作为头像时,组件内部的 `<img>` 元素
* @csspart icon - 使用图标作为头像时,组件内部的 `<mdui-icon>` 元素
*
* @cssprop --shape-corner - 组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
*/
export declare class Avatar extends MduiElement<AvatarEventMap> {
static styles: CSSResultGroup;
/**
* 头像图片的 URL 地址
*/
src?: string;
/**
* 图片如何适应容器框,与原生的 [`object-fit`](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) 属性相同。可选值包括:
*
* * `contain`:保持图片原有尺寸比例,内容会被等比例缩放
* * `cover`:保持图片原有尺寸比例,但部分内容可能被剪切
* * `fill`:默认值,不保持图片原有尺寸比例,内容会被拉伸以填充整个容器
* * `none`:保留图片原有尺寸,内容不会被缩放或拉伸
* * `scale-down`:保持图片原有尺寸比例,内容尺寸与 `none` 或 `contain` 中较小的一个相同
*/
fit?: /*保持图片原有尺寸比例,内容会被等比例缩放*/ 'contain' | /*保持图片原有尺寸比例,但部分内容可能被剪切*/ 'cover' | /*默认值,不保持图片原有尺寸比例,内容会被拉伸以填充整个容器*/ 'fill' | /*保留图片原有尺寸,内容不会被缩放或拉伸*/ 'none' | /*保持图片原有尺寸比例,内容尺寸与 `none` 或 `contain` 中较小的一个相同*/ 'scale-down';
/**
* 头像的 Material Icons 图标名
*/
icon?: string;
/**
* 头像的替代文本描述
*/
label?: string;
private readonly hasSlotController;
protected render(): TemplateResult;
}
export interface AvatarEventMap {
}
declare global {
interface HTMLElementTagNameMap {
'mdui-avatar': Avatar;
}
}

View File

@@ -0,0 +1,57 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { styleMap } from 'lit/directives/style-map.js';
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import { HasSlotController } from '@mdui/shared/controllers/has-slot.js';
import { nothingTemplate } from '@mdui/shared/helpers/template.js';
import { componentStyle } from '@mdui/shared/lit-styles/component-style.js';
import '../icon.js';
import { style } from './style.js';
/**
* @summary 头像组件
*
* ```html
* <mdui-avatar src="https://avatars.githubusercontent.com/u/3030330?s=40&v=4"></mdui-avatar>
* ```
*
* @slot - 自定义头像内容,可以为字母、汉字、`<img>` 元素、图标等
*
* @csspart image - 使用图片作为头像时,组件内部的 `<img>` 元素
* @csspart icon - 使用图标作为头像时,组件内部的 `<mdui-icon>` 元素
*
* @cssprop --shape-corner - 组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
*/
let Avatar = class Avatar extends MduiElement {
constructor() {
super(...arguments);
this.hasSlotController = new HasSlotController(this, '[default]');
}
render() {
return this.hasSlotController.test('[default]')
? html `<slot></slot>`
: this.src
? html `<img part="image" alt="${ifDefined(this.label)}" src="${this.src}" style="${styleMap({ objectFit: this.fit })}">`
: this.icon
? html `<mdui-icon part="icon" name="${this.icon}"></mdui-icon>`
: nothingTemplate;
}
};
Avatar.styles = [componentStyle, style];
__decorate([
property({ reflect: true })
], Avatar.prototype, "src", void 0);
__decorate([
property({ reflect: true })
], Avatar.prototype, "fit", void 0);
__decorate([
property({ reflect: true })
], Avatar.prototype, "icon", void 0);
__decorate([
property({ reflect: true })
], Avatar.prototype, "label", void 0);
Avatar = __decorate([
customElement('mdui-avatar')
], Avatar);
export { Avatar };

View File

@@ -0,0 +1 @@
export declare const style: import("lit").CSSResult;

View File

@@ -0,0 +1,2 @@
import { css } from 'lit';
export const style = css `:host{--shape-corner:var(--mdui-shape-corner-full);position:relative;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;overflow:hidden;white-space:nowrap;vertical-align:middle;border-radius:var(--shape-corner);-webkit-user-select:none;user-select:none;width:2.5rem;height:2.5rem;background-color:rgb(var(--mdui-color-primary-container));color:rgb(var(--mdui-color-on-primary-container));font-size:var(--mdui-typescale-title-medium-size);font-weight:var(--mdui-typescale-title-medium-weight);letter-spacing:var(--mdui-typescale-title-medium-tracking);line-height:var(--mdui-typescale-title-medium-line-height)}img{width:100%;height:100%}::slotted(mdui-icon),mdui-icon{font-size:1.5em}`;

View File

@@ -0,0 +1 @@
export * from './badge/index.js';

View File

@@ -0,0 +1 @@
export * from './badge/index.js';

View File

@@ -0,0 +1,31 @@
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import type { CSSResultGroup, TemplateResult } from 'lit';
/**
* @summary 徽标组件
*
* ```html
* <mdui-badge>12</mdui-badge>
* ```
*
* @slot - 徽标中显示的文本
*
* @cssprop --shape-corner - 组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
*/
export declare class Badge extends MduiElement<BadgeEventMap> {
static styles: CSSResultGroup;
/**
* 徽标的形状。可选值包括:
*
* * `small`:小型徽标,不显示文本
* * `large`:大型徽标,会显示文本
*/
variant: /*小型徽标,不显示文本*/ 'small' | /*大型徽标,会显示文本*/ 'large';
protected render(): TemplateResult;
}
export interface BadgeEventMap {
}
declare global {
interface HTMLElementTagNameMap {
'mdui-badge': Badge;
}
}

View File

@@ -0,0 +1,44 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import { nothingTemplate } from '@mdui/shared/helpers/template.js';
import { componentStyle } from '@mdui/shared/lit-styles/component-style.js';
import { style } from './style.js';
/**
* @summary 徽标组件
*
* ```html
* <mdui-badge>12</mdui-badge>
* ```
*
* @slot - 徽标中显示的文本
*
* @cssprop --shape-corner - 组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
*/
let Badge = class Badge extends MduiElement {
constructor() {
super(...arguments);
/**
* 徽标的形状。可选值包括:
*
* * `small`:小型徽标,不显示文本
* * `large`:大型徽标,会显示文本
*/
this.variant = 'large';
}
render() {
if (this.variant === 'small') {
return nothingTemplate;
}
return html `<slot></slot>`;
}
};
Badge.styles = [componentStyle, style];
__decorate([
property({ reflect: true })
], Badge.prototype, "variant", void 0);
Badge = __decorate([
customElement('mdui-badge')
], Badge);
export { Badge };

View File

@@ -0,0 +1 @@
export declare const style: import("lit").CSSResult;

View File

@@ -0,0 +1,2 @@
import { css } from 'lit';
export const style = css `:host{--shape-corner:var(--mdui-shape-corner-full);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;border-radius:var(--shape-corner);padding-left:.25rem;padding-right:.25rem;color:rgb(var(--mdui-color-on-error));background-color:rgb(var(--mdui-color-error));height:1rem;min-width:1rem;font-size:var(--mdui-typescale-label-small-size);font-weight:var(--mdui-typescale-label-small-weight);letter-spacing:var(--mdui-typescale-label-small-tracking);line-height:var(--mdui-typescale-label-small-line-height)}:host([variant=small]){min-width:0;padding:0;width:.375rem;height:.375rem}`;

View File

@@ -0,0 +1 @@
export * from './bottom-app-bar/index.js';

View File

@@ -0,0 +1 @@
export * from './bottom-app-bar/index.js';

View File

@@ -0,0 +1,67 @@
import { LayoutItemBase } from '../layout/layout-item-base.js';
import type { LayoutPlacement } from '../layout/helper.js';
import type { ScrollPaddingPosition } from '@mdui/shared/mixins/scrollBehavior.js';
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
declare const BottomAppBar_base: import("@lit/reactive-element/decorators/base.js").Constructor<import("@mdui/shared/mixins/scrollBehavior.js").ScrollBehaviorMixinInterface> & typeof LayoutItemBase;
/**
* @summary 底部应用栏组件
*
* ```html
* <mdui-bottom-app-bar>
* ..<mdui-button-icon icon="check_box--outlined"></mdui-button-icon>
* ..<mdui-button-icon icon="edit--outlined"></mdui-button-icon>
* ..<mdui-button-icon icon="mic_none--outlined"></mdui-button-icon>
* ..<mdui-button-icon icon="image--outlined"></mdui-button-icon>
* ..<div style="flex-grow: 1"></div>
* ..<mdui-fab icon="add"></mdui-fab>
* </mdui-bottom-app-bar>
* ```
*
* @event show - 开始显示时,事件被触发。可以通过调用 `event.preventDefault()` 阻止显示
* @event shown - 显示动画完成时,事件被触发
* @event hide - 开始隐藏时,事件被触发。可以通过调用 `event.preventDefault()` 阻止隐藏
* @event hidden - 隐藏动画完成时,事件被触发
*
* @slot - 底部应用栏内部的元素
*
* @cssprop --shape-corner - 组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
* @cssprop --z-index - 组件的 CSS `z-index` 值
*/
export declare class BottomAppBar extends BottomAppBar_base<BottomAppBarEventMap> {
static styles: CSSResultGroup;
/**
* 是否隐藏
*/
hide: boolean;
/**
* 是否让底部应用栏中的 [`<mdui-fab>`](/docs/2/components/fab) 组件脱离应用栏。如果为 `true`,则当应用栏隐藏后,[`<mdui-fab>`](/docs/2/components/fab) 仍会停留在页面上
*/
fabDetach: boolean;
/**
* 滚动行为。可选值为:
*
* * `hide`:滚动时隐藏
*/
scrollBehavior?: 'hide';
protected get scrollPaddingPosition(): ScrollPaddingPosition;
protected get layoutPlacement(): LayoutPlacement;
protected firstUpdated(_changedProperties: PropertyValues): void;
protected render(): TemplateResult;
/**
* 滚动行为
* 当前仅支持 hide 这一个行为,所以不做行为类型判断
*/
protected runScrollThreshold(isScrollingUp: boolean): void;
}
export interface BottomAppBarEventMap {
show: CustomEvent<void>;
shown: CustomEvent<void>;
hide: CustomEvent<void>;
hidden: CustomEvent<void>;
}
declare global {
interface HTMLElementTagNameMap {
'mdui-bottom-app-bar': BottomAppBar;
}
}
export {};

View File

@@ -0,0 +1,105 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { booleanConverter } from '@mdui/shared/helpers/decorator.js';
import { componentStyle } from '@mdui/shared/lit-styles/component-style.js';
import { ScrollBehaviorMixin } from '@mdui/shared/mixins/scrollBehavior.js';
import { LayoutItemBase } from '../layout/layout-item-base.js';
import { style } from './style.js';
/**
* @summary 底部应用栏组件
*
* ```html
* <mdui-bottom-app-bar>
* ..<mdui-button-icon icon="check_box--outlined"></mdui-button-icon>
* ..<mdui-button-icon icon="edit--outlined"></mdui-button-icon>
* ..<mdui-button-icon icon="mic_none--outlined"></mdui-button-icon>
* ..<mdui-button-icon icon="image--outlined"></mdui-button-icon>
* ..<div style="flex-grow: 1"></div>
* ..<mdui-fab icon="add"></mdui-fab>
* </mdui-bottom-app-bar>
* ```
*
* @event show - 开始显示时,事件被触发。可以通过调用 `event.preventDefault()` 阻止显示
* @event shown - 显示动画完成时,事件被触发
* @event hide - 开始隐藏时,事件被触发。可以通过调用 `event.preventDefault()` 阻止隐藏
* @event hidden - 隐藏动画完成时,事件被触发
*
* @slot - 底部应用栏内部的元素
*
* @cssprop --shape-corner - 组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
* @cssprop --z-index - 组件的 CSS `z-index` 值
*/
let BottomAppBar = class BottomAppBar extends ScrollBehaviorMixin(LayoutItemBase) {
constructor() {
super(...arguments);
/**
* 是否隐藏
*/
this.hide = false;
/**
* 是否让底部应用栏中的 [`<mdui-fab>`](/docs/2/components/fab) 组件脱离应用栏。如果为 `true`,则当应用栏隐藏后,[`<mdui-fab>`](/docs/2/components/fab) 仍会停留在页面上
*/
this.fabDetach = false;
}
get scrollPaddingPosition() {
return 'bottom';
}
get layoutPlacement() {
return 'bottom';
}
firstUpdated(_changedProperties) {
super.firstUpdated(_changedProperties);
this.addEventListener('transitionend', (event) => {
if (event.target === this) {
this.emit(this.hide ? 'hidden' : 'shown');
}
});
}
render() {
return html `<slot></slot>`;
}
/**
* 滚动行为
* 当前仅支持 hide 这一个行为,所以不做行为类型判断
*/
runScrollThreshold(isScrollingUp) {
// 向下滚动
if (!isScrollingUp && !this.hide) {
const eventProceeded = this.emit('hide', { cancelable: true });
if (eventProceeded) {
this.hide = true;
}
}
// 向上滚动
if (isScrollingUp && this.hide) {
const eventProceeded = this.emit('show', { cancelable: true });
if (eventProceeded) {
this.hide = false;
}
}
}
};
BottomAppBar.styles = [componentStyle, style];
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], BottomAppBar.prototype, "hide", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
attribute: 'fab-detach',
})
], BottomAppBar.prototype, "fabDetach", void 0);
__decorate([
property({ reflect: true, attribute: 'scroll-behavior' })
], BottomAppBar.prototype, "scrollBehavior", void 0);
BottomAppBar = __decorate([
customElement('mdui-bottom-app-bar')
], BottomAppBar);
export { BottomAppBar };

View File

@@ -0,0 +1 @@
export declare const style: import("lit").CSSResult;

View File

@@ -0,0 +1,2 @@
import { css } from 'lit';
export const style = css `:host{--shape-corner:var(--mdui-shape-corner-none);--z-index:2000;position:fixed;right:0;bottom:0;left:0;display:flex;flex:0 0 auto;align-items:center;justify-content:flex-start;border-radius:var(--shape-corner) var(--shape-corner) 0 0;z-index:var(--z-index);transition:bottom var(--mdui-motion-duration-long2) var(--mdui-motion-easing-emphasized);padding:0 1rem;height:5rem;background-color:rgb(var(--mdui-color-surface-container));box-shadow:var(--mdui-elevation-level2)}:host([scroll-target]:not([scroll-target=''])){position:absolute}:host([hide]:not([hide=false i])){transition-duration:var(--mdui-motion-duration-short4);bottom:-5.625rem}::slotted(:not(:first-child)){margin-left:.5rem}::slotted(mdui-fab){box-shadow:var(--mdui-elevation-level0)}:host([fab-detach]:not([fab-detach=false i])) ::slotted(mdui-fab){position:absolute;transition:bottom var(--mdui-motion-duration-long2) var(--mdui-motion-easing-standard);right:1rem;bottom:.75rem}:host([fab-detach][hide][scroll-behavior~=hide]:not([hide=false i],[fab-detach=false i])) ::slotted(mdui-fab){transition-duration:var(--mdui-motion-duration-short4);bottom:1rem;box-shadow:var(--mdui-elevation-level2)}:host([fab-detach][hide][scroll-behavior~=hide][scroll-target]:not([fab-detach=false i],[hide=false i],[scroll-target=''])) ::slotted(mdui-fab){bottom:6.625rem}:host([hide]:not([hide=false i])) ::slotted(:not(mdui-fab)),:host([hide]:not([hide=false i],[fab-detach])) ::slotted(mdui-fab),:host([hide][fab-detach=false i]:not([hide=false i])) ::slotted(mdui-fab){transform:translateY(8.75rem);transition:transform var(--mdui-motion-duration-0) var(--mdui-motion-easing-emphasized-accelerate) var(--mdui-motion-duration-short4)}::slotted(:first-child){transition:transform var(--mdui-motion-duration-short3) var(--mdui-motion-easing-emphasized-decelerate) var(--mdui-motion-duration-short1)}::slotted(:nth-child(2)){transition:transform var(--mdui-motion-duration-short3) var(--mdui-motion-easing-emphasized-decelerate) var(--mdui-motion-duration-short3)}::slotted(:nth-child(3)){transition:transform var(--mdui-motion-duration-short3) var(--mdui-motion-easing-emphasized-decelerate) var(--mdui-motion-duration-short4)}::slotted(:nth-child(4)){transition:transform var(--mdui-motion-duration-short3) var(--mdui-motion-easing-emphasized-decelerate) var(--mdui-motion-duration-medium1)}::slotted(:nth-child(5)){transition:transform var(--mdui-motion-duration-short3) var(--mdui-motion-easing-emphasized-decelerate) var(--mdui-motion-duration-medium2)}::slotted(:nth-child(6)){transition:transform var(--mdui-motion-duration-short3) var(--mdui-motion-easing-emphasized-decelerate) var(--mdui-motion-duration-medium3)}`;

View File

@@ -0,0 +1 @@
export * from './button-icon/index.js';

View File

@@ -0,0 +1 @@
export * from './button-icon/index.js';

View File

@@ -0,0 +1,72 @@
import { ButtonBase } from '../button/button-base.js';
import '../icon.js';
import type { Ripple } from '../ripple/index.js';
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
/**
* @summary 图标按钮组件
*
* ```html
* <mdui-button-icon icon="search"></mdui-button-icon>
* ```
*
* @event focus - 获得焦点时触发
* @event blur - 失去焦点时触发
* @event change - 选中状态变更时触发
* @event invalid - 表单字段验证未通过时触发
*
* @slot - 图标组件
* @slot selected-icon 选中状态显示的图标元素
*
* @csspart button - 内部的 `<button>` 或 `<a>` 元素
* @csspart icon - 未选中状态的图标
* @csspart selected-icon 选中状态的图标
* @csspart loading - 加载中状态的 `<mdui-circular-progress>` 元素
*
* @cssprop --shape-corner - 组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
*/
export declare class ButtonIcon extends ButtonBase<ButtonIconEventMap> {
static styles: CSSResultGroup;
/**
* 图标按钮的形状。可选值包括:
*
* * `standard`:适用于最低优先级的操作
* * `filled`:视觉效果强烈,适用于高优先级的操作
* * `tonal`:视觉效果介于 `filled` 和 `outlined` 之间,适用于中高优先级的操作
* * `outlined`:适用于中等优先级的操作
*/
variant: /*适用于最低优先级的操作*/ 'standard' | /*视觉效果强烈,适用于高优先级的操作*/ 'filled' | /*视觉效果介于 `filled` 和 `outlined` 之间,适用于中高优先级的操作*/ 'tonal' | /*适用于中等优先级的操作*/ 'outlined';
/**
* Material Icons 图标名。也可以通过 default slot 设置
*/
icon?: string;
/**
* 选中状态的 Material Icons 图标名。也可以通过 `slot="selected-icon"` 设置
*/
selectedIcon?: string;
/**
* 是否可选中
*/
selectable: boolean;
/**
* 是否已被选中
*/
selected: boolean;
private readonly rippleRef;
private readonly hasSlotController;
protected get rippleElement(): Ripple;
private onSelectedChange;
protected firstUpdated(changedProperties: PropertyValues): void;
protected render(): TemplateResult;
private renderIcon;
}
export interface ButtonIconEventMap {
focus: FocusEvent;
blur: FocusEvent;
change: CustomEvent<void>;
invalid: CustomEvent<void>;
}
declare global {
interface HTMLElementTagNameMap {
'mdui-button-icon': ButtonIcon;
}
}

View File

@@ -0,0 +1,129 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { createRef, ref } from 'lit/directives/ref.js';
import { HasSlotController } from '@mdui/shared/controllers/has-slot.js';
import { watch } from '@mdui/shared/decorators/watch.js';
import { booleanConverter } from '@mdui/shared/helpers/decorator.js';
import { nothingTemplate } from '@mdui/shared/helpers/template.js';
import { ButtonBase } from '../button/button-base.js';
import '../icon.js';
import { style } from './style.js';
/**
* @summary 图标按钮组件
*
* ```html
* <mdui-button-icon icon="search"></mdui-button-icon>
* ```
*
* @event focus - 获得焦点时触发
* @event blur - 失去焦点时触发
* @event change - 选中状态变更时触发
* @event invalid - 表单字段验证未通过时触发
*
* @slot - 图标组件
* @slot selected-icon 选中状态显示的图标元素
*
* @csspart button - 内部的 `<button>` 或 `<a>` 元素
* @csspart icon - 未选中状态的图标
* @csspart selected-icon 选中状态的图标
* @csspart loading - 加载中状态的 `<mdui-circular-progress>` 元素
*
* @cssprop --shape-corner - 组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
*/
let ButtonIcon = class ButtonIcon extends ButtonBase {
constructor() {
super(...arguments);
/**
* 图标按钮的形状。可选值包括:
*
* * `standard`:适用于最低优先级的操作
* * `filled`:视觉效果强烈,适用于高优先级的操作
* * `tonal`:视觉效果介于 `filled` 和 `outlined` 之间,适用于中高优先级的操作
* * `outlined`:适用于中等优先级的操作
*/
this.variant = 'standard';
/**
* 是否可选中
*/
this.selectable = false;
/**
* 是否已被选中
*/
this.selected = false;
this.rippleRef = createRef();
this.hasSlotController = new HasSlotController(this, '[default]', 'selected-icon');
}
get rippleElement() {
return this.rippleRef.value;
}
onSelectedChange() {
this.emit('change');
}
firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
this.addEventListener('click', () => {
if (!this.selectable || this.disabled) {
return;
}
this.selected = !this.selected;
});
}
render() {
return html `<mdui-ripple ${ref(this.rippleRef)} .noRipple="${this.noRipple}"></mdui-ripple>${this.isButton()
? this.renderButton({
className: 'button',
part: 'button',
content: this.renderIcon(),
})
: this.disabled || this.loading
? html `<span part="button" class="button _a">${this.renderIcon()}</span>`
: this.renderAnchor({
className: 'button',
part: 'button',
content: this.renderIcon(),
})} ${this.renderLoading()}`;
}
renderIcon() {
const icon = () => this.hasSlotController.test('[default]')
? html `<slot></slot>`
: this.icon
? html `<mdui-icon part="icon" class="icon" name="${this.icon}"></mdui-icon>`
: nothingTemplate;
const selectedIcon = () => this.hasSlotController.test('selected-icon') || this.selectedIcon
? html `<slot name="selected-icon" part="selected-icon" class="selected-icon"><mdui-icon name="${this.selectedIcon}"></mdui-icon></slot>`
: icon();
return this.selected ? selectedIcon() : icon();
}
};
ButtonIcon.styles = [ButtonBase.styles, style];
__decorate([
property({ reflect: true })
], ButtonIcon.prototype, "variant", void 0);
__decorate([
property({ reflect: true })
], ButtonIcon.prototype, "icon", void 0);
__decorate([
property({ reflect: true, attribute: 'selected-icon' })
], ButtonIcon.prototype, "selectedIcon", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], ButtonIcon.prototype, "selectable", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], ButtonIcon.prototype, "selected", void 0);
__decorate([
watch('selected', true)
], ButtonIcon.prototype, "onSelectedChange", null);
ButtonIcon = __decorate([
customElement('mdui-button-icon')
], ButtonIcon);
export { ButtonIcon };

View File

@@ -0,0 +1 @@
export declare const style: import("lit").CSSResult;

View File

@@ -0,0 +1,4 @@
import { css } from 'lit';
export const style = css `:host{--shape-corner:var(--mdui-shape-corner-full);position:relative;display:inline-block;flex-shrink:0;overflow:hidden;text-align:center;border-radius:var(--shape-corner);cursor:pointer;-webkit-tap-highlight-color:transparent;font-size:1.5rem;width:2.5rem;height:2.5rem}:host([variant=standard]){color:rgb(var(--mdui-color-on-surface-variant));--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface-variant)}:host([variant=filled]){color:rgb(var(--mdui-color-primary));background-color:rgb(var(--mdui-color-surface-container-highest));--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([variant=tonal]){color:rgb(var(--mdui-color-on-surface-variant));background-color:rgb(var(--mdui-color-surface-container-highest));--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface-variant)}:host([variant=outlined]){border:.0625rem solid rgb(var(--mdui-color-outline));color:rgb(var(--mdui-color-on-surface-variant));--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface-variant)}:host([variant=outlined][pressed]){color:rgb(var(--mdui-color-on-surface));--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}:host([variant=standard][selected]:not([selected=false i])){color:rgb(var(--mdui-color-primary));--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([variant=filled]:not([selectable])),:host([variant=filled][selectable=false i]),:host([variant=filled][selected]:not([selected=false i])){color:rgb(var(--mdui-color-on-primary));background-color:rgb(var(--mdui-color-primary));--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-primary)}:host([variant=tonal]:not([selectable])),:host([variant=tonal][selectable=false i]),:host([variant=tonal][selected]:not([selected=false i])){color:rgb(var(--mdui-color-on-secondary-container));background-color:rgb(var(--mdui-color-secondary-container));--mdui-comp-ripple-state-layer-color:var(
--mdui-color-on-secondary-container
)}:host([variant=outlined][selected]:not([selected=false i])){border:none;color:rgb(var(--mdui-color-inverse-on-surface));background-color:rgb(var(--mdui-color-inverse-surface));--mdui-comp-ripple-state-layer-color:var(--mdui-color-inverse-on-surface)}:host([variant=filled][disabled]:not([disabled=false i])),:host([variant=outlined][disabled]:not([disabled=false i])),:host([variant=tonal][disabled]:not([disabled=false i])){background-color:rgba(var(--mdui-color-on-surface),.12);border-color:rgba(var(--mdui-color-on-surface),.12)}:host([disabled]:not([disabled=false i])),:host([loading]:not([loading=false i])){cursor:default;pointer-events:none}:host([disabled]:not([disabled=false i])){color:rgba(var(--mdui-color-on-surface),.38)!important}.button{float:left;width:100%}:host([loading]:not([loading=false i])) .button,:host([loading]:not([loading=false i])) mdui-ripple{opacity:0}.icon,.selected-icon mdui-icon,::slotted(*){font-size:inherit}mdui-circular-progress{display:flex;position:absolute;top:calc(50% - 1.5rem / 2);left:calc(50% - 1.5rem / 2);width:1.5rem;height:1.5rem}:host([variant=filled]:not([disabled])) mdui-circular-progress,:host([variant=filled][disabled=false i]) mdui-circular-progress{stroke:rgb(var(--mdui-color-on-primary))}:host([disabled]:not([disabled=false i])) mdui-circular-progress{stroke:rgba(var(--mdui-color-on-surface),38%)}`;

View File

@@ -0,0 +1 @@
export * from './button/index.js';

View File

@@ -0,0 +1 @@
export * from './button/index.js';

View File

@@ -0,0 +1 @@
export declare const buttonBaseStyle: import("lit").CSSResult;

View File

@@ -0,0 +1,2 @@
import { css } from 'lit';
export const buttonBaseStyle = css `.button{position:relative;display:inline-flex;align-items:center;justify-content:center;height:100%;padding:0;overflow:hidden;color:inherit;font-size:inherit;font-family:inherit;font-weight:inherit;letter-spacing:inherit;white-space:nowrap;text-align:center;text-decoration:none;vertical-align:middle;background:0 0;border:none;outline:0;cursor:inherit;-webkit-user-select:none;user-select:none;touch-action:manipulation;zoom:1;-webkit-user-drag:none}`;

View File

@@ -0,0 +1,137 @@
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import '../circular-progress.js';
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
type RenderButtonOptions = {
id?: string;
className?: string;
part?: string;
content?: TemplateResult | TemplateResult[];
tabindex?: number;
};
declare const ButtonBase_base: import("@lit/reactive-element/decorators/base.js").Constructor<import("@mdui/shared/mixins/anchor.js").AnchorMixinInterface> & import("@lit/reactive-element/decorators/base.js").Constructor<import("../ripple/ripple-mixin.js").RippleMixinInterface> & import("@lit/reactive-element/decorators/base.js").Constructor<import("@mdui/shared/mixins/focusable.js").FocusableMixinInterface> & typeof MduiElement;
export declare class ButtonBase<E> extends ButtonBase_base<E> {
static styles: CSSResultGroup;
/**
* 是否禁用
*/
disabled: boolean;
/**
* 是否处于加载中状态
*/
loading: boolean;
/**
* 按钮的名称,将与表单数据一起提交。
*
* **Note**:仅在未设置 `href` 属性时,此属性才有效。
*/
name: string;
/**
* 按钮的初始值,将与表单数据一起提交。
*
* **Note**:仅在未设置 `href` 属性时,此属性才有效。
*/
value: string;
/**
* 按钮的类型。默认类型为 `button`。可选类型包括:
*
* * `submit`:点击按钮会提交表单数据到服务器
* * `reset`:点击按钮会将表单中的所有字段重置为初始值
* * `button`:此类型的按钮没有默认行为
*
* **Note**:仅在未指定 `href` 属性时,此属性才有效。
*/
type: /*此按钮将表单数据提交给服务器*/ 'submit' | /*此按钮重置所有组件为初始值*/ 'reset' | /*此按钮没有默认行为*/ 'button';
/**
* 关联的 `<form>` 元素。此属性值应为同一页面中的一个 `<form>` 元素的 `id`。
*
* 如果未指定此属性,则该元素必须是 `<form>` 元素的子元素。通过此属性,你可以将元素放置在页面的任何位置,而不仅仅是 `<form>` 元素的子元素。
*
* **Note**:仅在未指定 `href` 属性时,此属性才有效。
*/
form?: string;
/**
* 指定提交表单的 URL。
*
* 如果指定了此属性,将覆盖 `<form>` 元素的 `action` 属性。
*
* **Note**:仅在未指定 `href` 属性且 `type="submit"` 时,此属性才有效。
*/
formAction?: string;
/**
* 指定提交表单到服务器的内容类型。可选值包括:
*
* * `application/x-www-form-urlencoded`:未指定该属性时的默认值
* * `multipart/form-data`:当表单包含 `<input type="file">` 元素时使用
* * `text/plain`HTML5 新增,用于调试
*
* 如果指定了此属性,将覆盖 `<form>` 元素的 `enctype` 属性。
*
* **Note**:仅在未指定 `href` 属性且 `type="submit"` 时,此属性才有效。
*/
formEnctype?: /*未指定该属性时的默认值*/ 'application/x-www-form-urlencoded' | /*当表单包含 `<input type="file">` 元素时使用*/ 'multipart/form-data' | /*HTML5 新增,用于调试*/ 'text/plain';
/**
* 指定提交表单时使用的 HTTP 方法。可选值包括:
*
* * `post`:表单数据包含在表单内容中,发送到服务器
* * `get`:表单数据以 `?` 作为分隔符附加到表单的 URI 属性中,生成的 URI 发送到服务器。当表单没有副作用,并且仅包含 ASCII 字符时,使用此方法
*
* 如果设置了此属性,将覆盖 `<form>` 元素的 `method` 属性。
*
* **Note**:仅在未设置 `href` 属性且 `type="submit"` 时,此属性才有效。
*/
formMethod?: /*表单数据包含在表单内容中,发送到服务器*/ 'post' | /*表单数据以 `?` 作为分隔符附加到表单的 URI 属性中,生成的 URI 发送到服务器。当表单没有副作用,并且仅包含 ASCII 字符时,使用此方法*/ 'get';
/**
* 如果设置了此属性,表单提交时将不执行表单验证。
*
* 如果设置了此属性,将覆盖 `<form>` 元素的 `novalidate` 属性。
*
* **Note**:仅在未设置 `href` 属性且 `type="submit"` 时,此属性才有效。
*/
formNoValidate: boolean;
/**
* 提交表单后接收到的响应应显示在何处。可选值包括:
*
* * `_self`:默认选项,在当前框架中打开
* * `_blank`:在新窗口中打开
* * `_parent`:在父框架中打开
* * `_top`:在整个窗口中打开
*
* 如果设置了此属性,将覆盖 `<form>` 元素的 `target` 属性。
*
* **Note**:仅在未设置 `href` 属性且 `type="submit"` 时,此属性才有效。
*/
formTarget?: /*默认选项,在当前框架中打开*/ '_self' | /*在新窗口中打开*/ '_blank' | /*在父框架中打开*/ '_parent' | /*在整个窗口中打开*/ '_top';
private readonly formController;
/**
* 表单验证状态对象,具体参见 [`ValidityState`](https://developer.mozilla.org/zh-CN/docs/Web/API/ValidityState)
*/
get validity(): ValidityState | undefined;
/**
* 如果表单验证未通过,此属性将包含提示信息。如果验证通过,此属性将为空字符串
*/
get validationMessage(): string | undefined;
protected get rippleDisabled(): boolean;
protected get focusElement(): HTMLElement | null;
protected get focusDisabled(): boolean;
/**
* 检查表单字段是否通过验证。如果未通过,返回 `false` 并触发 `invalid` 事件;如果通过,返回 `true`
*/
checkValidity(): boolean;
/**
* 检查表单字段是否通过验证。如果未通过,返回 `false` 并触发 `invalid` 事件;如果通过,返回 `true`。
*
* 如果验证未通过,还会在组件上显示验证失败的提示。
*/
reportValidity(): boolean;
/**
* 设置自定义的错误提示文本。只要这个文本不为空,就表示字段未通过验证
*
* @param message 自定义的错误提示文本
*/
setCustomValidity(message: string): void;
protected firstUpdated(_changedProperties: PropertyValues): void;
protected renderLoading(): TemplateResult;
protected renderButton({ id, className, part, content, }: RenderButtonOptions): TemplateResult;
protected isButton(): boolean;
}
export {};

View File

@@ -0,0 +1,209 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { property } from 'lit/decorators.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import cc from 'classcat';
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import { FormController } from '@mdui/shared/controllers/form.js';
import { booleanConverter } from '@mdui/shared/helpers/decorator.js';
import { nothingTemplate } from '@mdui/shared/helpers/template.js';
import { componentStyle } from '@mdui/shared/lit-styles/component-style.js';
import { AnchorMixin } from '@mdui/shared/mixins/anchor.js';
import { FocusableMixin } from '@mdui/shared/mixins/focusable.js';
import '../circular-progress.js';
import { RippleMixin } from '../ripple/ripple-mixin.js';
import { buttonBaseStyle } from './button-base-style.js';
export class ButtonBase extends AnchorMixin(RippleMixin(FocusableMixin(MduiElement))) {
constructor() {
super(...arguments);
/**
* 是否禁用
*/
this.disabled = false;
/**
* 是否处于加载中状态
*/
this.loading = false;
/**
* 按钮的名称,将与表单数据一起提交。
*
* **Note**:仅在未设置 `href` 属性时,此属性才有效。
*/
this.name = '';
/**
* 按钮的初始值,将与表单数据一起提交。
*
* **Note**:仅在未设置 `href` 属性时,此属性才有效。
*/
this.value = '';
/**
* 按钮的类型。默认类型为 `button`。可选类型包括:
*
* * `submit`:点击按钮会提交表单数据到服务器
* * `reset`:点击按钮会将表单中的所有字段重置为初始值
* * `button`:此类型的按钮没有默认行为
*
* **Note**:仅在未指定 `href` 属性时,此属性才有效。
*/
this.type = 'button';
/**
* 如果设置了此属性,表单提交时将不执行表单验证。
*
* 如果设置了此属性,将覆盖 `<form>` 元素的 `novalidate` 属性。
*
* **Note**:仅在未设置 `href` 属性且 `type="submit"` 时,此属性才有效。
*/
this.formNoValidate = false;
this.formController = new FormController(this);
}
/**
* 表单验证状态对象,具体参见 [`ValidityState`](https://developer.mozilla.org/zh-CN/docs/Web/API/ValidityState)
*/
get validity() {
if (this.isButton()) {
return this.focusElement.validity;
}
}
/**
* 如果表单验证未通过,此属性将包含提示信息。如果验证通过,此属性将为空字符串
*/
get validationMessage() {
if (this.isButton()) {
return this.focusElement.validationMessage;
}
}
get rippleDisabled() {
return this.disabled || this.loading;
}
get focusElement() {
return this.isButton()
? this.renderRoot?.querySelector('._button')
: !this.focusDisabled
? this.renderRoot?.querySelector('._a')
: this;
}
get focusDisabled() {
return this.disabled || this.loading;
}
/**
* 检查表单字段是否通过验证。如果未通过,返回 `false` 并触发 `invalid` 事件;如果通过,返回 `true`
*/
checkValidity() {
if (this.isButton()) {
const valid = this.focusElement.checkValidity();
if (!valid) {
// @ts-ignore
this.emit('invalid', {
bubbles: false,
cancelable: true,
composed: false,
});
}
return valid;
}
return true;
}
/**
* 检查表单字段是否通过验证。如果未通过,返回 `false` 并触发 `invalid` 事件;如果通过,返回 `true`。
*
* 如果验证未通过,还会在组件上显示验证失败的提示。
*/
reportValidity() {
if (this.isButton()) {
const invalid = !this.focusElement.reportValidity();
if (invalid) {
// @ts-ignore
this.emit('invalid', {
bubbles: false,
cancelable: true,
composed: false,
});
// todo 考虑是否要支持 preventDefault() 方法,当前 invalid 状态没有样式
}
return !invalid;
}
return true;
}
/**
* 设置自定义的错误提示文本。只要这个文本不为空,就表示字段未通过验证
*
* @param message 自定义的错误提示文本
*/
setCustomValidity(message) {
if (this.isButton()) {
this.focusElement.setCustomValidity(message);
}
}
firstUpdated(_changedProperties) {
super.firstUpdated(_changedProperties);
this.addEventListener('click', () => {
if (this.type === 'submit') {
this.formController.submit(this);
}
if (this.type === 'reset') {
this.formController.reset(this);
}
});
}
renderLoading() {
return this.loading
? html `<mdui-circular-progress part="loading"></mdui-circular-progress>`
: nothingTemplate;
}
renderButton({ id, className, part, content = html `<slot></slot>`, }) {
return html `<button id="${ifDefined(id)}" class="${cc(['_button', className])}" part="${ifDefined(part)}" ?disabled="${this.rippleDisabled || this.focusDisabled}">${content}</button>`;
}
isButton() {
return !this.href;
}
}
ButtonBase.styles = [
componentStyle,
buttonBaseStyle,
];
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], ButtonBase.prototype, "disabled", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], ButtonBase.prototype, "loading", void 0);
__decorate([
property({ reflect: true })
], ButtonBase.prototype, "name", void 0);
__decorate([
property({ reflect: true })
], ButtonBase.prototype, "value", void 0);
__decorate([
property({ reflect: true })
], ButtonBase.prototype, "type", void 0);
__decorate([
property({ reflect: true })
], ButtonBase.prototype, "form", void 0);
__decorate([
property({ reflect: true, attribute: 'formaction' })
], ButtonBase.prototype, "formAction", void 0);
__decorate([
property({ reflect: true, attribute: 'formenctype' })
], ButtonBase.prototype, "formEnctype", void 0);
__decorate([
property({ reflect: true, attribute: 'formmethod' })
], ButtonBase.prototype, "formMethod", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
attribute: 'formnovalidate',
})
], ButtonBase.prototype, "formNoValidate", void 0);
__decorate([
property({ reflect: true, attribute: 'formtarget' })
], ButtonBase.prototype, "formTarget", void 0);

View File

@@ -0,0 +1,69 @@
import '../icon.js';
import { ButtonBase } from './button-base.js';
import type { Ripple } from '../ripple/index.js';
import type { TemplateResult, CSSResultGroup } from 'lit';
/**
* @summary 按钮组件
*
* ```html
* <mdui-button>Button</mdui-button>
* ```
*
* @event focus - 获得焦点时触发
* @event blur - 失去焦点时触发
* @event invalid - 表单字段验证未通过时触发
*
* @slot - 按钮的文本
* @slot icon - 按钮左侧的元素
* @slot end-icon - 按钮右侧的元素
*
* @csspart button - 内部的 `<button>` 或 `<a>` 元素
* @csspart label - 按钮的文本
* @csspart icon - 按钮左侧的图标
* @csspart end-icon - 按钮右侧的图标
* @csspart loading - 加载中状态的 `<mdui-circular-progress>` 元素
*
* @cssprop --shape-corner - 组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
*/
export declare class Button extends ButtonBase<ButtonEventMap> {
static styles: CSSResultGroup;
/**
* 按钮的形状。可选值包括:
*
* * `elevated`:带阴影的按钮,适用于需要将按钮与背景视觉分离的场景
* * `filled`:视觉效果强烈,适用于重要流程的最终操作,如“保存”、“确认”等
* * `tonal`:视觉效果介于 `filled` 和 `outlined` 之间,适用于中高优先级的操作,如流程中的“下一步”
* * `outlined`:带边框的按钮,适用于中等优先级,且次要的操作,如“返回”
* * `text`:文本按钮,适用于最低优先级的操作
*/
variant: /*带阴影的按钮,适用于需要将按钮与背景视觉分离的场景*/ 'elevated' | /*视觉效果强烈,适用于重要流程的最终操作,如“保存”、“确认”等*/ 'filled' | /*视觉效果介于 `filled` 和 `outlined` 之间,适用于中高优先级的操作,如流程中的“下一步”*/ 'tonal' | /*带边框的按钮,适用于中等优先级,且次要的操作,如“返回”*/ 'outlined' | /*文本按钮,适用于最低优先级的操作*/ 'text';
/**
* 是否填满父元素宽度
*/
fullWidth: boolean;
/**
* 左侧的 Material Icons 图标名。也可以通过 `slot="icon"` 设置
*/
icon?: string;
/**
* 右侧的 Material Icons 图标名。也可以通过 `slot="end-icon"` 设置
*/
endIcon?: string;
private readonly rippleRef;
protected get rippleElement(): Ripple;
protected render(): TemplateResult;
private renderIcon;
private renderLabel;
private renderEndIcon;
private renderInner;
}
export interface ButtonEventMap {
focus: FocusEvent;
blur: FocusEvent;
invalid: CustomEvent<void>;
}
declare global {
interface HTMLElementTagNameMap {
'mdui-button': Button;
}
}

View File

@@ -0,0 +1,111 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { createRef, ref } from 'lit/directives/ref.js';
import { booleanConverter } from '@mdui/shared/helpers/decorator.js';
import { nothingTemplate } from '@mdui/shared/helpers/template.js';
import '../icon.js';
import { ButtonBase } from './button-base.js';
import { style } from './style.js';
/**
* @summary 按钮组件
*
* ```html
* <mdui-button>Button</mdui-button>
* ```
*
* @event focus - 获得焦点时触发
* @event blur - 失去焦点时触发
* @event invalid - 表单字段验证未通过时触发
*
* @slot - 按钮的文本
* @slot icon - 按钮左侧的元素
* @slot end-icon - 按钮右侧的元素
*
* @csspart button - 内部的 `<button>` 或 `<a>` 元素
* @csspart label - 按钮的文本
* @csspart icon - 按钮左侧的图标
* @csspart end-icon - 按钮右侧的图标
* @csspart loading - 加载中状态的 `<mdui-circular-progress>` 元素
*
* @cssprop --shape-corner - 组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
*/
let Button = class Button extends ButtonBase {
constructor() {
super(...arguments);
/**
* 按钮的形状。可选值包括:
*
* * `elevated`:带阴影的按钮,适用于需要将按钮与背景视觉分离的场景
* * `filled`:视觉效果强烈,适用于重要流程的最终操作,如“保存”、“确认”等
* * `tonal`:视觉效果介于 `filled` 和 `outlined` 之间,适用于中高优先级的操作,如流程中的“下一步”
* * `outlined`:带边框的按钮,适用于中等优先级,且次要的操作,如“返回”
* * `text`:文本按钮,适用于最低优先级的操作
*/
this.variant = 'filled';
/**
* 是否填满父元素宽度
*/
this.fullWidth = false;
this.rippleRef = createRef();
}
get rippleElement() {
return this.rippleRef.value;
}
render() {
return html `<mdui-ripple ${ref(this.rippleRef)} .noRipple="${this.noRipple}"></mdui-ripple>${this.isButton()
? this.renderButton({
className: 'button',
part: 'button',
content: this.renderInner(),
})
: this.disabled || this.loading
? html `<span part="button" class="button _a">${this.renderInner()}</span>`
: this.renderAnchor({
className: 'button',
part: 'button',
content: this.renderInner(),
})}`;
}
renderIcon() {
if (this.loading) {
return this.renderLoading();
}
return html `<slot name="icon" part="icon" class="icon">${this.icon
? html `<mdui-icon name="${this.icon}"></mdui-icon>`
: nothingTemplate}</slot>`;
}
renderLabel() {
return html `<slot part="label" class="label"></slot>`;
}
renderEndIcon() {
return html `<slot name="end-icon" part="end-icon" class="end-icon">${this.endIcon
? html `<mdui-icon name="${this.endIcon}"></mdui-icon>`
: nothingTemplate}</slot>`;
}
renderInner() {
return [this.renderIcon(), this.renderLabel(), this.renderEndIcon()];
}
};
Button.styles = [ButtonBase.styles, style];
__decorate([
property({ reflect: true })
], Button.prototype, "variant", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
attribute: 'full-width',
})
], Button.prototype, "fullWidth", void 0);
__decorate([
property({ reflect: true })
], Button.prototype, "icon", void 0);
__decorate([
property({ reflect: true, attribute: 'end-icon' })
], Button.prototype, "endIcon", void 0);
Button = __decorate([
customElement('mdui-button')
], Button);
export { Button };

View File

@@ -0,0 +1 @@
export declare const style: import("lit").CSSResult;

View File

@@ -0,0 +1,4 @@
import { css } from 'lit';
export const style = css `:host{--shape-corner:var(--mdui-shape-corner-full);position:relative;display:inline-block;flex-shrink:0;overflow:hidden;text-align:center;border-radius:var(--shape-corner);cursor:pointer;-webkit-tap-highlight-color:transparent;transition:box-shadow var(--mdui-motion-duration-short4) var(--mdui-motion-easing-linear);min-width:3rem;height:2.5rem;color:rgb(var(--mdui-color-primary));font-size:var(--mdui-typescale-label-large-size);font-weight:var(--mdui-typescale-label-large-weight);letter-spacing:var(--mdui-typescale-label-large-tracking);line-height:var(--mdui-typescale-label-large-line-height)}.button{width:100%;padding:0 1rem}:host([full-width]:not([full-width=false i])){display:block}:host([variant=elevated]){box-shadow:var(--mdui-elevation-level1);background-color:rgb(var(--mdui-color-surface-container-low));--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([variant=filled]){color:rgb(var(--mdui-color-on-primary));background-color:rgb(var(--mdui-color-primary));--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-primary)}:host([variant=tonal]){color:rgb(var(--mdui-color-on-secondary-container));background-color:rgb(var(--mdui-color-secondary-container));--mdui-comp-ripple-state-layer-color:var(
--mdui-color-on-secondary-container
)}:host([variant=outlined]){border:.0625rem solid rgb(var(--mdui-color-outline));--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([variant=text]){--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([variant=outlined][focus-visible]){border-color:rgb(var(--mdui-color-primary))}:host([variant=elevated][hover]){box-shadow:var(--mdui-elevation-level2)}:host([variant=filled][hover]),:host([variant=tonal][hover]){box-shadow:var(--mdui-elevation-level1)}:host([disabled]:not([disabled=false i])),:host([loading]:not([loading=false i])){cursor:default;pointer-events:none}:host([disabled]:not([disabled=false i])){color:rgba(var(--mdui-color-on-surface),38%);box-shadow:var(--mdui-elevation-level0)}:host([variant=elevated][disabled]:not([disabled=false i])),:host([variant=filled][disabled]:not([disabled=false i])),:host([variant=tonal][disabled]:not([disabled=false i])){background-color:rgba(var(--mdui-color-on-surface),12%)}:host([variant=outlined][disabled]:not([disabled=false i])){border-color:rgba(var(--mdui-color-on-surface),12%)}.label{display:inline-flex;padding-right:.5rem;padding-left:.5rem}.end-icon,.icon{display:inline-flex;font-size:1.28571429em}.end-icon mdui-icon,.icon mdui-icon,::slotted([slot=end-icon]),::slotted([slot=icon]){font-size:inherit}mdui-circular-progress{display:inline-flex;width:1.125rem;height:1.125rem}:host([variant=filled]) mdui-circular-progress{stroke:rgb(var(--mdui-color-on-primary))}:host([variant=tonal]) mdui-circular-progress{stroke:rgb(var(--mdui-color-on-secondary-container))}:host([disabled]:not([disabled=false i])) mdui-circular-progress{stroke:rgba(var(--mdui-color-on-surface),38%)}`;

View File

@@ -0,0 +1 @@
export * from './card/index.js';

View File

@@ -0,0 +1 @@
export * from './card/index.js';

View File

@@ -0,0 +1,53 @@
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import type { Ripple } from '../ripple/index.js';
import type { TemplateResult, CSSResultGroup } from 'lit';
declare const Card_base: import("@lit/reactive-element/decorators/base.js").Constructor<import("@mdui/shared/mixins/anchor.js").AnchorMixinInterface> & import("@lit/reactive-element/decorators/base.js").Constructor<import("../ripple/ripple-mixin.js").RippleMixinInterface> & import("@lit/reactive-element/decorators/base.js").Constructor<import("@mdui/shared/mixins/focusable.js").FocusableMixinInterface> & typeof MduiElement;
/**
* @summary 卡片组件
*
* ```html
* <mdui-card>card content</mdui-card>
* ```
*
* @event focus - 获得焦点时触发
* @event blur - 失去焦点时触发
*
* @slot - 卡片的内容
*
* @cssprop --shape-corner - 组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
*/
export declare class Card extends Card_base<CardEventMap> {
static styles: CSSResultGroup;
/**
* 卡片的形状。可选值包括:
*
* * `elevated`:带阴影的卡片,与背景的视觉分离度较高
* * `filled`:带填充色的卡片,与背景的视觉分离度较低
* * `outlined`:带边框的卡片,与背景的视觉分离度最高
*/
variant: /*带阴影的卡片,与背景的视觉分离度较高*/ 'elevated' | /*带填充色的卡片,与背景的视觉分离度较低*/ 'filled' | /*带边框的卡片,与背景的视觉分离度最高*/ 'outlined';
/**
* 是否可点击。为 `true` 时,卡片将具有鼠标悬浮效果和点击涟漪效果
*/
clickable: boolean;
/**
* 是否禁用
*/
disabled: boolean;
private readonly rippleRef;
protected get rippleElement(): Ripple;
protected get rippleDisabled(): boolean;
protected get focusElement(): HTMLElement | null;
protected get focusDisabled(): boolean;
protected render(): TemplateResult;
}
export interface CardEventMap {
focus: FocusEvent;
blur: FocusEvent;
}
declare global {
interface HTMLElementTagNameMap {
'mdui-card': Card;
}
}
export {};

View File

@@ -0,0 +1,91 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { createRef, ref } from 'lit/directives/ref.js';
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import { booleanConverter } from '@mdui/shared/helpers/decorator.js';
import { componentStyle } from '@mdui/shared/lit-styles/component-style.js';
import { AnchorMixin } from '@mdui/shared/mixins/anchor.js';
import { FocusableMixin } from '@mdui/shared/mixins/focusable.js';
import { RippleMixin } from '../ripple/ripple-mixin.js';
import { style } from './style.js';
/**
* @summary 卡片组件
*
* ```html
* <mdui-card>card content</mdui-card>
* ```
*
* @event focus - 获得焦点时触发
* @event blur - 失去焦点时触发
*
* @slot - 卡片的内容
*
* @cssprop --shape-corner - 组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
*/
let Card = class Card extends AnchorMixin(RippleMixin(FocusableMixin(MduiElement))) {
constructor() {
super(...arguments);
/**
* 卡片的形状。可选值包括:
*
* * `elevated`:带阴影的卡片,与背景的视觉分离度较高
* * `filled`:带填充色的卡片,与背景的视觉分离度较低
* * `outlined`:带边框的卡片,与背景的视觉分离度最高
*/
this.variant = 'elevated';
/**
* 是否可点击。为 `true` 时,卡片将具有鼠标悬浮效果和点击涟漪效果
*/
this.clickable = false;
/**
* 是否禁用
*/
this.disabled = false;
this.rippleRef = createRef();
}
get rippleElement() {
return this.rippleRef.value;
}
get rippleDisabled() {
return this.disabled || (!this.href && !this.clickable);
}
get focusElement() {
return this.href && !this.disabled
? this.renderRoot.querySelector('._a')
: this;
}
get focusDisabled() {
return this.rippleDisabled;
}
render() {
return html `<mdui-ripple ${ref(this.rippleRef)} .noRipple="${this.noRipple}"></mdui-ripple>${this.href && !this.disabled
? this.renderAnchor({
className: 'link',
content: html `<slot></slot>`,
})
: html `<slot></slot>`}`;
}
};
Card.styles = [componentStyle, style];
__decorate([
property({ reflect: true })
], Card.prototype, "variant", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Card.prototype, "clickable", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Card.prototype, "disabled", void 0);
Card = __decorate([
customElement('mdui-card')
], Card);
export { Card };

View File

@@ -0,0 +1 @@
export declare const style: import("lit").CSSResult;

View File

@@ -0,0 +1,2 @@
import { css } from 'lit';
export const style = css `:host{--shape-corner:var(--mdui-shape-corner-medium);position:relative;display:inline-block;overflow:hidden;border-radius:var(--shape-corner);-webkit-tap-highlight-color:transparent;transition:box-shadow var(--mdui-motion-duration-short4) var(--mdui-motion-easing-linear);--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}:host([clickable]:not([clickable=false i])){cursor:pointer}:host([variant=elevated]){background-color:rgb(var(--mdui-color-surface-container-low));box-shadow:var(--mdui-elevation-level1)}:host([variant=filled]){background-color:rgb(var(--mdui-color-surface-container-highest))}:host([variant=outlined]){background-color:rgb(var(--mdui-color-surface));border:.0625rem solid rgb(var(--mdui-color-outline))}:host([variant=elevated][hover]){box-shadow:var(--mdui-elevation-level2)}:host([variant=filled][hover]),:host([variant=outlined][hover]){box-shadow:var(--mdui-elevation-level1)}:host([variant=elevated][dragged]),:host([variant=filled][dragged]),:host([variant=outlined][dragged]){box-shadow:var(--mdui-elevation-level3)}:host([disabled]:not([disabled=false i])){opacity:.38;cursor:default;-webkit-user-select:none;user-select:none}:host([variant=elevated][disabled]:not([disabled=false i])){background-color:rgb(var(--mdui-color-surface-variant));box-shadow:var(--mdui-elevation-level0)}:host([variant=filled][disabled]:not([disabled=false i])){background-color:rgb(var(--mdui-color-surface));box-shadow:var(--mdui-elevation-level1)}:host([variant=outlined][disabled]:not([disabled=false i])){box-shadow:var(--mdui-elevation-level0);border-color:rgba(var(--mdui-color-outline),.32)}.link{position:relative;display:inline-block;width:100%;height:100%;color:inherit;font-size:inherit;letter-spacing:inherit;text-decoration:none;touch-action:manipulation;-webkit-user-drag:none}`;

View File

@@ -0,0 +1 @@
export * from './checkbox/index.js';

View File

@@ -0,0 +1 @@
export * from './checkbox/index.js';

View File

@@ -0,0 +1,137 @@
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import '@mdui/shared/icons/check-box-outline-blank.js';
import '@mdui/shared/icons/check-box.js';
import '@mdui/shared/icons/indeterminate-check-box.js';
import '../icon.js';
import type { Ripple } from '../ripple/index.js';
import type { FormControl } from '@mdui/jq/shared/form.js';
import type { TemplateResult, CSSResultGroup } from 'lit';
declare const Checkbox_base: import("@lit/reactive-element/decorators/base.js").Constructor<import("../ripple/ripple-mixin.js").RippleMixinInterface> & import("@lit/reactive-element/decorators/base.js").Constructor<import("@mdui/shared/mixins/focusable.js").FocusableMixinInterface> & typeof MduiElement;
/**
* @summary 复选框组件
*
* ```html
* <mdui-checkbox>Checkbox</mdui-checkbox>
* ```
*
* @event focus - 获得焦点时触发
* @event blur - 失去焦点时触发
* @event change - 选中状态变更时触发
* @event input - 选中状态变更时触发
* @event invalid - 表单字段验证未通过时触发
*
* @slot - 复选框文本
* @slot unchecked-icon - 未选中状态的图标
* @slot checked-icon - 选中状态的图标
* @slot indeterminate-icon - 不确定状态的图标
*
* @csspart control - 左侧图标容器
* @csspart unchecked-icon - 未选中状态的图标
* @csspart checked-icon - 选中状态的图标
* @csspart indeterminate-icon - 不确定状态的图标
* @csspart label - 复选框文本
*/
export declare class Checkbox extends Checkbox_base<CheckboxEventMap> implements FormControl {
static styles: CSSResultGroup;
/**
* 是否为禁用状态
*/
disabled: boolean;
/**
* 是否为选中状态
*/
checked: boolean;
/**
* 默认选中状态。在重置表单时,将恢复为此状态。此属性只能通过 JavaScript 属性设置
*/
defaultChecked: boolean;
/**
* 是否处于不确定状态
*/
indeterminate: boolean;
/**
* 提交表单时,是否必须选中此复选框
*/
required: boolean;
/**
* 关联的 `<form>` 元素。此属性值应为同一页面中的一个 `<form>` 元素的 `id`。
*
* 如果未指定此属性,则该元素必须是 `<form>` 元素的子元素。通过此属性,你可以将元素放置在页面的任何位置,而不仅仅是 `<form>` 元素的子元素。
*/
form?: string;
/**
* 复选框名称,将与表单数据一起提交
*/
name: string;
/**
* 复选框的值,将于表单数据一起提交
*/
value: string;
/**
* 未选中状态的 Material Icons 图标名。也可以通过 `slot="unchecked-icon"` 设置
*/
uncheckedIcon?: string;
/**
* 选中状态的 Material Icons 图标名。也可以通过 `slot="checked-icon"` 设置
*/
checkedIcon?: string;
/**
* 不确定状态的 Material Icons 图标名。也可以通过 `slot="indeterminate-icon"` 设置
*/
indeterminateIcon?: string;
/**
* 是否验证未通过
*/
private invalid;
private readonly inputRef;
private readonly rippleRef;
private readonly formController;
/**
* 表单验证状态对象,具体参见 [`ValidityState`](https://developer.mozilla.org/zh-CN/docs/Web/API/ValidityState)
*/
get validity(): ValidityState;
/**
* 如果表单验证未通过,此属性将包含提示信息。如果验证通过,此属性将为空字符串
*/
get validationMessage(): string;
protected get rippleElement(): Ripple;
protected get rippleDisabled(): boolean;
protected get focusElement(): HTMLElement | undefined;
protected get focusDisabled(): boolean;
private onDisabledChange;
private onCheckedChange;
/**
* 检查表单字段是否通过验证。如果未通过,返回 `false` 并触发 `invalid` 事件;如果通过,返回 `true`
*/
checkValidity(): boolean;
/**
* 检查表单字段是否通过验证。如果未通过,返回 `false` 并触发 `invalid` 事件;如果通过,返回 `true`。
*
* 如果验证未通过,还会在组件上显示验证失败的提示。
*/
reportValidity(): boolean;
/**
* 设置自定义的错误提示文本。只要这个文本不为空,就表示字段未通过验证
*
* @param message 自定义的错误提示文本
*/
setCustomValidity(message: string): void;
protected render(): TemplateResult;
/**
* input[type="checkbox"] 的 change 事件无法冒泡越过 shadow dom
*/
private onChange;
}
export interface CheckboxEventMap {
focus: FocusEvent;
blur: FocusEvent;
change: CustomEvent<void>;
input: Event;
invalid: CustomEvent<void>;
}
declare global {
interface HTMLElementTagNameMap {
'mdui-checkbox': Checkbox;
}
}
export {};

View File

@@ -0,0 +1,254 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { live } from 'lit/directives/live.js';
import { createRef, ref } from 'lit/directives/ref.js';
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import { FormController, formResets } from '@mdui/shared/controllers/form.js';
import { defaultValue } from '@mdui/shared/decorators/default-value.js';
import { watch } from '@mdui/shared/decorators/watch.js';
import { booleanConverter } from '@mdui/shared/helpers/decorator.js';
import '@mdui/shared/icons/check-box-outline-blank.js';
import '@mdui/shared/icons/check-box.js';
import '@mdui/shared/icons/indeterminate-check-box.js';
import { componentStyle } from '@mdui/shared/lit-styles/component-style.js';
import { FocusableMixin } from '@mdui/shared/mixins/focusable.js';
import '../icon.js';
import { RippleMixin } from '../ripple/ripple-mixin.js';
import { style } from './style.js';
/**
* @summary 复选框组件
*
* ```html
* <mdui-checkbox>Checkbox</mdui-checkbox>
* ```
*
* @event focus - 获得焦点时触发
* @event blur - 失去焦点时触发
* @event change - 选中状态变更时触发
* @event input - 选中状态变更时触发
* @event invalid - 表单字段验证未通过时触发
*
* @slot - 复选框文本
* @slot unchecked-icon - 未选中状态的图标
* @slot checked-icon - 选中状态的图标
* @slot indeterminate-icon - 不确定状态的图标
*
* @csspart control - 左侧图标容器
* @csspart unchecked-icon - 未选中状态的图标
* @csspart checked-icon - 选中状态的图标
* @csspart indeterminate-icon - 不确定状态的图标
* @csspart label - 复选框文本
*/
let Checkbox = class Checkbox extends RippleMixin(FocusableMixin(MduiElement)) {
constructor() {
super(...arguments);
/**
* 是否为禁用状态
*/
this.disabled = false;
/**
* 是否为选中状态
*/
this.checked = false;
/**
* 默认选中状态。在重置表单时,将恢复为此状态。此属性只能通过 JavaScript 属性设置
*/
this.defaultChecked = false;
/**
* 是否处于不确定状态
*/
this.indeterminate = false;
/**
* 提交表单时,是否必须选中此复选框
*/
this.required = false;
/**
* 复选框名称,将与表单数据一起提交
*/
this.name = '';
/**
* 复选框的值,将于表单数据一起提交
*/
this.value = 'on';
/**
* 是否验证未通过
*/
this.invalid = false;
this.inputRef = createRef();
this.rippleRef = createRef();
this.formController = new FormController(this, {
value: (control) => (control.checked ? control.value : undefined),
defaultValue: (control) => control.defaultChecked,
setValue: (control, checked) => (control.checked = checked),
});
}
/**
* 表单验证状态对象,具体参见 [`ValidityState`](https://developer.mozilla.org/zh-CN/docs/Web/API/ValidityState)
*/
get validity() {
return this.inputRef.value.validity;
}
/**
* 如果表单验证未通过,此属性将包含提示信息。如果验证通过,此属性将为空字符串
*/
get validationMessage() {
return this.inputRef.value.validationMessage;
}
get rippleElement() {
return this.rippleRef.value;
}
get rippleDisabled() {
return this.disabled;
}
get focusElement() {
return this.inputRef.value;
}
get focusDisabled() {
return this.disabled;
}
async onDisabledChange() {
await this.updateComplete;
this.invalid = !this.inputRef.value.checkValidity();
}
async onCheckedChange() {
await this.updateComplete;
// reset 引起的值变更,不执行验证;直接修改值引起的变更,需要进行验证
const form = this.formController.getForm();
if (form && formResets.get(form)?.has(this)) {
this.invalid = false;
formResets.get(form).delete(this);
}
else {
this.invalid = !this.inputRef.value.checkValidity();
}
}
/**
* 检查表单字段是否通过验证。如果未通过,返回 `false` 并触发 `invalid` 事件;如果通过,返回 `true`
*/
checkValidity() {
const valid = this.inputRef.value.checkValidity();
if (!valid) {
this.emit('invalid', {
bubbles: false,
cancelable: true,
composed: false,
});
}
return valid;
}
/**
* 检查表单字段是否通过验证。如果未通过,返回 `false` 并触发 `invalid` 事件;如果通过,返回 `true`。
*
* 如果验证未通过,还会在组件上显示验证失败的提示。
*/
reportValidity() {
this.invalid = !this.inputRef.value.reportValidity();
if (this.invalid) {
const eventProceeded = this.emit('invalid', {
bubbles: false,
cancelable: true,
composed: false,
});
// 调用了 preventDefault() 时,隐藏默认的表单错误提示
if (!eventProceeded) {
this.blur();
this.focus();
}
}
return !this.invalid;
}
/**
* 设置自定义的错误提示文本。只要这个文本不为空,就表示字段未通过验证
*
* @param message 自定义的错误提示文本
*/
setCustomValidity(message) {
this.inputRef.value.setCustomValidity(message);
this.invalid = !this.inputRef.value.checkValidity();
}
render() {
return html `<label class="${classMap({ invalid: this.invalid })}"><input ${ref(this.inputRef)} type="checkbox" name="${ifDefined(this.name)}" value="${ifDefined(this.value)}" .indeterminate="${live(this.indeterminate)}" .disabled="${this.disabled}" .checked="${live(this.checked)}" .required="${this.required}" @change="${this.onChange}"> <i part="control"><mdui-ripple ${ref(this.rippleRef)} .noRipple="${this.noRipple}"></mdui-ripple><slot name="unchecked-icon" part="unchecked-icon" class="icon unchecked-icon">${this.uncheckedIcon
? html `<mdui-icon name="${this.uncheckedIcon}" class="i"></mdui-icon>`
: html `<mdui-icon-check-box-outline-blank class="i"></mdui-icon-check-box-outline-blank>`}</slot><slot name="checked-icon" part="checked-icon" class="icon checked-icon">${this.checkedIcon
? html `<mdui-icon name="${this.checkedIcon}" class="i"></mdui-icon>`
: html `<mdui-icon-check-box class="i"></mdui-icon-check-box>`}</slot><slot name="indeterminate-icon" part="indeterminate-icon" class="icon indeterminate-icon">${this.indeterminateIcon
? html `<mdui-icon name="${this.indeterminateIcon}" class="i"></mdui-icon>`
: html `<mdui-icon-indeterminate-check-box class="i"></mdui-icon-indeterminate-check-box>`}</slot></i><slot part="label" class="label"></slot></label>`;
}
/**
* input[type="checkbox"] 的 change 事件无法冒泡越过 shadow dom
*/
onChange() {
this.checked = this.inputRef.value.checked;
this.indeterminate = false;
this.emit('change');
}
};
Checkbox.styles = [componentStyle, style];
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Checkbox.prototype, "disabled", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Checkbox.prototype, "checked", void 0);
__decorate([
defaultValue('checked')
], Checkbox.prototype, "defaultChecked", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Checkbox.prototype, "indeterminate", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Checkbox.prototype, "required", void 0);
__decorate([
property({ reflect: true })
], Checkbox.prototype, "form", void 0);
__decorate([
property({ reflect: true })
], Checkbox.prototype, "name", void 0);
__decorate([
property({ reflect: true })
], Checkbox.prototype, "value", void 0);
__decorate([
property({ reflect: true, attribute: 'unchecked-icon' })
], Checkbox.prototype, "uncheckedIcon", void 0);
__decorate([
property({ reflect: true, attribute: 'checked-icon' })
], Checkbox.prototype, "checkedIcon", void 0);
__decorate([
property({ reflect: true, attribute: 'indeterminate-icon' })
], Checkbox.prototype, "indeterminateIcon", void 0);
__decorate([
state()
], Checkbox.prototype, "invalid", void 0);
__decorate([
watch('disabled', true),
watch('indeterminate', true),
watch('required', true)
], Checkbox.prototype, "onDisabledChange", null);
__decorate([
watch('checked', true)
], Checkbox.prototype, "onCheckedChange", null);
Checkbox = __decorate([
customElement('mdui-checkbox')
], Checkbox);
export { Checkbox };

View File

@@ -0,0 +1 @@
export declare const style: import("lit").CSSResult;

View File

@@ -0,0 +1,2 @@
import { css } from 'lit';
export const style = css `:host{position:relative;display:inline-flex;cursor:pointer;-webkit-tap-highlight-color:transparent;border-radius:.125rem;font-size:var(--mdui-typescale-label-large-size);font-weight:var(--mdui-typescale-label-large-weight);letter-spacing:var(--mdui-typescale-label-large-tracking);line-height:var(--mdui-typescale-label-large-line-height)}label{display:inline-flex;align-items:center;width:100%;cursor:inherit;-webkit-user-select:none;user-select:none;touch-action:manipulation;zoom:1;-webkit-user-drag:none}input{position:absolute;padding:0;opacity:0;pointer-events:none;width:1.125rem;height:1.125rem;margin:0 0 0 .6875rem}.icon{display:flex;position:absolute;opacity:1;transform:scale(1);color:rgb(var(--mdui-color-on-surface));font-size:1.5rem;transition:color var(--mdui-motion-duration-short4) var(--mdui-motion-easing-standard)}.checked-icon,.indeterminate-icon{opacity:0;transform:scale(.5);transition-property:color,opacity,transform;transition-duration:var(--mdui-motion-duration-short4);transition-timing-function:var(--mdui-motion-easing-standard)}.icon .i,::slotted([slot=checked-icon]),::slotted([slot=indeterminate-icon]),::slotted([slot=unchecked-icon]){color:inherit;font-size:inherit}i{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0;overflow:hidden;border-radius:50%;width:2.5rem;height:2.5rem;--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}.label{display:flex;width:100%;padding-top:.625rem;padding-bottom:.625rem;color:rgb(var(--mdui-color-on-surface));transition:color var(--mdui-motion-duration-short4) var(--mdui-motion-easing-standard)}:host([checked]:not([checked=false i])) i{--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([checked]:not([checked=false i])) .icon{color:rgb(var(--mdui-color-primary))}:host([checked]:not([checked=false i])) .indeterminate-icon{opacity:0;transform:scale(.5)}:host([checked]:not([checked=false i])) .checked-icon{opacity:1;transform:scale(1)}:host([indeterminate]:not([indeterminate=false i])) i{--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([indeterminate]:not([indeterminate=false i])) .icon{color:rgb(var(--mdui-color-primary))}:host([indeterminate]:not([indeterminate=false i])) .checked-icon{opacity:0;transform:scale(.5)}:host([indeterminate]:not([indeterminate=false i])) .indeterminate-icon{opacity:1;transform:scale(1)}.invalid i{--mdui-comp-ripple-state-layer-color:var(--mdui-color-error)}.invalid .icon{color:rgb(var(--mdui-color-error))}.invalid .label{color:rgb(var(--mdui-color-error))}:host([disabled]:not([disabled=false i])){cursor:default;pointer-events:none}:host([disabled]:not([disabled=false i])) .icon{color:rgba(var(--mdui-color-on-surface),38%)}:host([disabled]:not([disabled=false i])) .label{color:rgba(var(--mdui-color-on-surface),38%)}:host([disabled][checked]:not([disabled=false i],[checked=false i])) .unchecked-icon,:host([disabled][indeterminate]:not([disabled=false i],[indeterminate=false i])) .unchecked-icon{opacity:0}`;

View File

@@ -0,0 +1 @@
export * from './chip/index.js';

View File

@@ -0,0 +1 @@
export * from './chip/index.js';

View File

@@ -0,0 +1,109 @@
import '@mdui/shared/icons/check.js';
import '@mdui/shared/icons/clear.js';
import { ButtonBase } from '../button/button-base.js';
import '../icon.js';
import type { Ripple } from '../ripple/index.js';
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
/**
* @summary 纸片组件
*
* ```html
* <mdui-chip>Chip</mdui-chip>
* ```
*
* @event focus - 获得焦点时触发
* @event blur - 失去焦点时触发
* @event invalid - 表单字段验证未通过时触发
* @event change - 选中状态变更时触发
* @event delete - 点击删除图标时触发
*
* @slot - 纸片文本
* @slot icon - 左侧元素
* @slot end-icon - 右侧元素
* @slot selected-icon - 选中状态下的左侧元素
* @slot delete-icon - 可删除时的右侧删除元素
*
* @csspart button - 内部的 `<button>` 或 `<a>` 元素
* @csspart label - 纸片文本
* @csspart icon - 左侧图标
* @csspart end-icon - 右侧图标
* @csspart selected-icon - 选中状态下的左侧图标
* @csspart delete-icon - 可删除时的右侧删除图标
* @csspart loading - 加载中状态的 `<mdui-circular-progress>` 元素
*
* @cssprop --shape-corner - 组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
*/
export declare class Chip extends ButtonBase<ChipEventMap> {
static styles: CSSResultGroup;
/**
* 纸片的形状。可选值包括:
*
* * `assist`:用于显示与当前上下文相关的辅助操作,如在点餐页面提供分享、收藏等功能
* * `filter`:用于对内容进行筛选,如在搜索结果页过滤搜索结果
* * `input`:用于表示用户输入的信息片段,如在 Gmail 的“收件人”字段中的联系人
* * `suggestion`:用于提供动态生成的推荐信息,以简化用户操作,如在聊天应用中预测用户可能想发送的信息
*/
variant: /*用于显示与当前上下文相关的辅助操作,如在点餐页面提供分享、收藏等功能*/ 'assist' | /*用于对内容进行筛选,如在搜索结果页过滤搜索结果*/ 'filter' | /*用于表示用户输入的信息片段,如在 Gmail 的“收件人”字段中的联系人*/ 'input' | /*用于提供动态生成的推荐信息,以简化用户操作,如在聊天应用中预测用户可能想发送的信息*/ 'suggestion';
/**
* 是否显示阴影
*/
elevated: boolean;
/**
* 是否可选中
*/
selectable: boolean;
/**
* 是否已选中
*/
selected: boolean;
/**
* 是否可删除。为 `true` 时,纸片右侧会显示删除图标
*/
deletable: boolean;
/**
* 左侧的 Material Icons 图标名。也可以通过 `slot="icon"` 设置
*/
icon?: string;
/**
* 选中状态下左侧的 Material Icons 图标名。也可以通过 `slot="selected-icon"` 设置
*/
selectedIcon?: string;
/**
* 右侧的 Material Icons 图标名。也可以通过 `slot="end-icon"` 设置
*/
endIcon?: string;
/**
* 可删除时,右侧删除图标的 Material Icons 图标名。也可以通过 `slot="delete-icon"` 设置
*/
deleteIcon?: string;
private readonly rippleRef;
private readonly hasSlotController;
constructor();
protected get rippleElement(): Ripple;
private onSelectedChange;
protected firstUpdated(changedProperties: PropertyValues): void;
protected render(): TemplateResult;
private onClick;
private onKeyDown;
/**
* 点击删除按钮
*/
private onDelete;
private renderIcon;
private renderLabel;
private renderEndIcon;
private renderDeleteIcon;
private renderInner;
}
export interface ChipEventMap {
focus: FocusEvent;
blur: FocusEvent;
invalid: CustomEvent<void>;
change: CustomEvent<void>;
delete: CustomEvent<void>;
}
declare global {
interface HTMLElementTagNameMap {
'mdui-chip': Chip;
}
}

View File

@@ -0,0 +1,243 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { createRef, ref } from 'lit/directives/ref.js';
import cc from 'classcat';
import { HasSlotController } from '@mdui/shared/controllers/has-slot.js';
import { watch } from '@mdui/shared/decorators/watch.js';
import { booleanConverter } from '@mdui/shared/helpers/decorator.js';
import { nothingTemplate } from '@mdui/shared/helpers/template.js';
import '@mdui/shared/icons/check.js';
import '@mdui/shared/icons/clear.js';
import { ButtonBase } from '../button/button-base.js';
import '../icon.js';
import { style } from './style.js';
/**
* @summary 纸片组件
*
* ```html
* <mdui-chip>Chip</mdui-chip>
* ```
*
* @event focus - 获得焦点时触发
* @event blur - 失去焦点时触发
* @event invalid - 表单字段验证未通过时触发
* @event change - 选中状态变更时触发
* @event delete - 点击删除图标时触发
*
* @slot - 纸片文本
* @slot icon - 左侧元素
* @slot end-icon - 右侧元素
* @slot selected-icon - 选中状态下的左侧元素
* @slot delete-icon - 可删除时的右侧删除元素
*
* @csspart button - 内部的 `<button>` 或 `<a>` 元素
* @csspart label - 纸片文本
* @csspart icon - 左侧图标
* @csspart end-icon - 右侧图标
* @csspart selected-icon - 选中状态下的左侧图标
* @csspart delete-icon - 可删除时的右侧删除图标
* @csspart loading - 加载中状态的 `<mdui-circular-progress>` 元素
*
* @cssprop --shape-corner - 组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
*/
let Chip = class Chip extends ButtonBase {
constructor() {
super();
/**
* 纸片的形状。可选值包括:
*
* * `assist`:用于显示与当前上下文相关的辅助操作,如在点餐页面提供分享、收藏等功能
* * `filter`:用于对内容进行筛选,如在搜索结果页过滤搜索结果
* * `input`:用于表示用户输入的信息片段,如在 Gmail 的“收件人”字段中的联系人
* * `suggestion`:用于提供动态生成的推荐信息,以简化用户操作,如在聊天应用中预测用户可能想发送的信息
*/
this.variant = 'assist';
/**
* 是否显示阴影
*/
this.elevated = false;
/**
* 是否可选中
*/
this.selectable = false;
/**
* 是否已选中
*/
this.selected = false;
/**
* 是否可删除。为 `true` 时,纸片右侧会显示删除图标
*/
this.deletable = false;
this.rippleRef = createRef();
this.hasSlotController = new HasSlotController(this, 'icon', 'selected-icon', 'end-icon');
this.onClick = this.onClick.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
}
get rippleElement() {
return this.rippleRef.value;
}
onSelectedChange() {
this.emit('change');
}
firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
this.addEventListener('click', this.onClick);
this.addEventListener('keydown', this.onKeyDown);
}
render() {
const hasIcon = this.icon || this.hasSlotController.test('icon');
const hasEndIcon = this.endIcon || this.hasSlotController.test('end-icon');
const hasSelectedIcon = this.selectedIcon ||
['assist', 'filter'].includes(this.variant) ||
hasIcon ||
this.hasSlotController.test('selected-icon');
const className = cc({
button: true,
'has-icon': this.loading ||
(!this.selected && hasIcon) ||
(this.selected && hasSelectedIcon),
'has-end-icon': hasEndIcon,
});
return html `<mdui-ripple ${ref(this.rippleRef)} .noRipple="${this.noRipple}"></mdui-ripple>${this.isButton()
? this.renderButton({
className,
part: 'button',
content: this.renderInner(),
})
: this.disabled || this.loading
? html `<span part="button" class="${className} _a">${this.renderInner()}</span>`
: this.renderAnchor({
className,
part: 'button',
content: this.renderInner(),
})}`;
}
onClick() {
if (this.disabled || this.loading) {
return;
}
// 点击时,切换选中状态
if (this.selectable) {
this.selected = !this.selected;
}
}
onKeyDown(event) {
if (this.disabled || this.loading) {
return;
}
// 按下空格键时,切换选中状态
if (this.selectable && event.key === ' ') {
event.preventDefault();
this.selected = !this.selected;
}
// 按下 Delete 或 BackSpace 键时,触发 delete 事件
if (this.deletable && ['Delete', 'Backspace'].includes(event.key)) {
this.emit('delete');
}
}
/**
* 点击删除按钮
*/
onDelete(event) {
event.stopPropagation();
this.emit('delete');
}
renderIcon() {
if (this.loading) {
return this.renderLoading();
}
const icon = () => {
return this.icon
? html `<mdui-icon name="${this.icon}" class="i"></mdui-icon>`
: nothingTemplate;
};
const selectedIcon = () => {
if (this.selectedIcon) {
return html `<mdui-icon name="${this.selectedIcon}" class="i"></mdui-icon>`;
}
if (this.variant === 'assist' || this.variant === 'filter') {
return html `<mdui-icon-check class="i"></mdui-icon-check>`;
}
return icon();
};
return !this.selected
? html `<slot name="icon" part="icon" class="icon">${icon()}</slot>`
: html `<slot name="selected-icon" part="selected-icon" class="selected-icon">${selectedIcon()}</slot>`;
}
renderLabel() {
return html `<slot part="label" class="label"></slot>`;
}
renderEndIcon() {
return html `<slot name="end-icon" part="end-icon" class="end-icon">${this.endIcon
? html `<mdui-icon name="${this.endIcon}" class="i"></mdui-icon>`
: nothingTemplate}</slot>`;
}
renderDeleteIcon() {
if (!this.deletable) {
return nothingTemplate;
}
return html `<slot name="delete-icon" part="delete-icon" class="delete-icon" @click="${this.onDelete}">${this.deleteIcon
? html `<mdui-icon name="${this.deleteIcon}" class="i"></mdui-icon>`
: html `<mdui-icon-clear class="i"></mdui-icon-clear>`}</slot>`;
}
renderInner() {
return [
this.renderIcon(),
this.renderLabel(),
this.renderEndIcon(),
this.renderDeleteIcon(),
];
}
};
Chip.styles = [ButtonBase.styles, style];
__decorate([
property({ reflect: true })
], Chip.prototype, "variant", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Chip.prototype, "elevated", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Chip.prototype, "selectable", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Chip.prototype, "selected", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Chip.prototype, "deletable", void 0);
__decorate([
property({ reflect: true })
], Chip.prototype, "icon", void 0);
__decorate([
property({ reflect: true, attribute: 'selected-icon' })
], Chip.prototype, "selectedIcon", void 0);
__decorate([
property({ reflect: true, attribute: 'end-icon' })
], Chip.prototype, "endIcon", void 0);
__decorate([
property({ reflect: true, attribute: 'delete-icon' })
], Chip.prototype, "deleteIcon", void 0);
__decorate([
watch('selected', true)
], Chip.prototype, "onSelectedChange", null);
Chip = __decorate([
customElement('mdui-chip')
], Chip);
export { Chip };

View File

@@ -0,0 +1 @@
export declare const style: import("lit").CSSResult;

View File

@@ -0,0 +1,4 @@
import { css } from 'lit';
export const style = css `:host{--shape-corner:var(--mdui-shape-corner-small);position:relative;display:inline-block;flex-shrink:0;overflow:hidden;border-radius:var(--shape-corner);cursor:pointer;-webkit-tap-highlight-color:transparent;transition:box-shadow var(--mdui-motion-duration-short4) var(--mdui-motion-easing-linear);height:2rem;background-color:rgb(var(--mdui-color-surface));border:.0625rem solid rgb(var(--mdui-color-outline));color:rgb(var(--mdui-color-on-surface-variant));font-size:var(--mdui-typescale-label-large-size);font-weight:var(--mdui-typescale-label-large-weight);letter-spacing:var(--mdui-typescale-label-large-tracking);line-height:var(--mdui-typescale-label-large-line-height);--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface-variant)}.button{padding-right:.4375rem;padding-left:.4375rem}:host([variant=input]) .button{padding-right:.1875rem;padding-left:.1875rem}:host([selected]:not([selected=false i])) .button{padding-right:.5rem;padding-left:.5rem}:host([selected][variant=input]:not([selected=false i])) .button{padding-right:.25rem;padding-left:.25rem}:host([elevated]:not([elevated=false i])) .button{padding-right:.5rem;padding-left:.5rem}:host([variant=assist]){color:rgb(var(--mdui-color-on-surface));--mdui-comp-ripple-state-layer-color:var(--mdui-color-on-surface)}:host([elevated]:not([elevated=false i])){border-width:0;background-color:rgb(var(--mdui-color-surface-container-low));box-shadow:var(--mdui-elevation-level1)}:host([selected]:not([selected=false i])){color:rgb(var(--mdui-color-on-secondary-container));background-color:rgb(var(--mdui-color-secondary-container));border-width:0;--mdui-comp-ripple-state-layer-color:var(
--mdui-color-on-secondary-container
)}:host([disabled]:not([disabled=false i])),:host([loading]:not([loading=false i])){cursor:default;pointer-events:none}:host([disabled]:not([disabled=false i])){border-color:rgba(var(--mdui-color-on-surface),12%);color:rgba(var(--mdui-color-on-surface),38%);box-shadow:var(--mdui-elevation-level0)}:host([disabled][elevated]:not([disabled=false i],[elevated=false i])),:host([disabled][selected]:not([disabled=false i],[selected=false i])){background-color:rgba(var(--mdui-color-on-surface),12%)}:host([selected][hover]:not([selected=false i])){box-shadow:var(--mdui-elevation-level1)}:host([elevated][hover]:not([elevated=false i])){color:rgb(var(--mdui-color-on-secondary-container));box-shadow:var(--mdui-elevation-level2)}:host([variant=filter][hover]),:host([variant=input][hover]),:host([variant=suggestion][hover]){color:rgb(var(--mdui-color-on-surface-variant))}:host([variant=filter][focus-visible]),:host([variant=input][focus-visible]),:host([variant=suggestion][focus-visible]){border-color:rgb(var(--mdui-color-on-surface-variant))}:host([dragged]),:host([dragged][hover]){box-shadow:var(--mdui-elevation-level4)}.button{overflow:visible}.label{display:inline-flex;padding-right:.5rem;padding-left:.5rem}.end-icon,.icon,.selected-icon{display:inline-flex;font-size:1.28571429em;color:rgb(var(--mdui-color-on-surface-variant))}:host([variant=assist]) .end-icon,:host([variant=assist]) .icon,:host([variant=assist]) .selected-icon{color:rgb(var(--mdui-color-primary))}:host([selected]:not([selected=false i])) .end-icon,:host([selected]:not([selected=false i])) .icon,:host([selected]:not([selected=false i])) .selected-icon{color:rgb(var(--mdui-color-on-secondary-container))}:host([disabled]:not([disabled=false i])) .end-icon,:host([disabled]:not([disabled=false i])) .icon,:host([disabled]:not([disabled=false i])) .selected-icon{opacity:.38;color:rgb(var(--mdui-color-on-surface))}.end-icon .i,.icon .i,.selected-icon .i,::slotted([slot=end-icon]),::slotted([slot=icon]),::slotted([slot=selected-icon]){font-size:inherit}:host([variant=input]) .has-icon .icon,:host([variant=input]) .has-icon .selected-icon,:host([variant=input]) .has-icon mdui-circular-progress{margin-left:.25rem}:host([variant=input]) .has-end-icon .end-icon{margin-right:.25rem}mdui-circular-progress{display:inline-flex;width:1.125rem;height:1.125rem}:host([disabled]:not([disabled=false i])) mdui-circular-progress{stroke:rgba(var(--mdui-color-on-surface),38%)}::slotted(mdui-avatar[slot=end-icon]),::slotted(mdui-avatar[slot=icon]),::slotted(mdui-avatar[slot=selected-icon]){width:1.5rem;height:1.5rem}:host([disabled]:not([disabled=false i])) ::slotted(mdui-avatar[slot=end-icon]),:host([disabled]:not([disabled=false i])) ::slotted(mdui-avatar[slot=icon]),:host([disabled]:not([disabled=false i])) ::slotted(mdui-avatar[slot=selected-icon]){opacity:.38}::slotted(mdui-avatar[slot=icon]),::slotted(mdui-avatar[slot=selected-icon]){margin-left:-.25rem;margin-right:-.125rem}::slotted(mdui-avatar[slot=end-icon]){margin-right:-.25rem;margin-left:-.125rem}.delete-icon{display:inline-flex;font-size:1.28571429em;transition:background-color var(--mdui-motion-duration-short4) var(--mdui-motion-easing-linear);border-radius:var(--mdui-shape-corner-full);margin-right:-.25rem;margin-left:-.25rem;padding:.25rem;color:rgb(var(--mdui-color-on-surface-variant))}.delete-icon:hover{background-color:rgba(var(--mdui-color-on-surface-variant),12%)}.has-end-icon .delete-icon{margin-left:.25rem}:host([variant=assiat]) .delete-icon{color:rgb(var(--mdui-color-primary))}:host([variant=input]) .delete-icon{margin-right:.0625rem}:host([disabled]:not([disabled=false i])) .delete-icon{color:rgba(var(--mdui-color-on-surface),38%)}.delete-icon .i,::slotted([slot=delete-icon]){font-size:inherit}::slotted(mdui-avatar[slot=delete-icon]){width:1.125rem;height:1.125rem}`;

View File

@@ -0,0 +1 @@
export * from './circular-progress/index.js';

View File

@@ -0,0 +1 @@
export * from './circular-progress/index.js';

View File

@@ -0,0 +1,30 @@
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import type { CSSResultGroup, TemplateResult } from 'lit';
/**
* @summary 圆形进度指示器组件
*
* ```html
* <mdui-circular-progress></mdui-circular-progress>
* ```
*/
export declare class CircularProgress extends MduiElement<CircularProgressEventMap> {
static styles: CSSResultGroup;
/**
* 进度指示器的最大值。默认为 `1`
*/
max: number;
/**
* 进度指示器的当前值。如果未指定该值,则显示为不确定状态
*/
value?: number;
protected render(): TemplateResult;
private renderDeterminate;
private renderInDeterminate;
}
export interface CircularProgressEventMap {
}
declare global {
interface HTMLElementTagNameMap {
'mdui-circular-progress': CircularProgress;
}
}

View File

@@ -0,0 +1,62 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { isUndefined } from '@mdui/jq/shared/helper.js';
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import { componentStyle } from '@mdui/shared/lit-styles/component-style.js';
import { style } from './style.js';
/**
* @summary 圆形进度指示器组件
*
* ```html
* <mdui-circular-progress></mdui-circular-progress>
* ```
*/
let CircularProgress = class CircularProgress extends MduiElement {
constructor() {
super(...arguments);
/**
* 进度指示器的最大值。默认为 `1`
*/
this.max = 1;
}
render() {
const isDeterminate = !isUndefined(this.value);
return html `<div class="progress ${classMap({
determinate: isDeterminate,
indeterminate: !isDeterminate,
})}">${isDeterminate ? this.renderDeterminate() : this.renderInDeterminate()}</div>`;
}
renderDeterminate() {
const value = this.value;
const strokeWidth = 4; // 圆环宽度
const circleRadius = 18; // 圆环宽度中心点的半径
const π = 3.1415926;
const center = circleRadius + strokeWidth / 2;
const circumference = 2 * π * circleRadius;
const determinateStrokeDashOffset = (1 - value / Math.max(this.max ?? value, value)) * circumference;
return html `<svg viewBox="0 0 ${center * 2} ${center * 2}"><circle class="track" cx="${center}" cy="${center}" r="${circleRadius}" stroke-width="${strokeWidth}"></circle><circle class="circle" cx="${center}" cy="${center}" r="${circleRadius}" stroke-dasharray="${2 * π * circleRadius}" stroke-dashoffset="${determinateStrokeDashOffset}" stroke-width="${strokeWidth}"></circle></svg>`;
}
renderInDeterminate() {
const strokeWidth = 4; // 圆环宽度
const circleRadius = 18; // 圆环宽度中心点的半径
const π = 3.1415926;
const center = circleRadius + strokeWidth / 2;
const circumference = 2 * π * circleRadius;
const halfCircumference = 0.5 * circumference;
const circle = (thisStrokeWidth) => html `<svg class="circle" viewBox="0 0 ${center * 2} ${center * 2}"><circle cx="${center}" cy="${center}" r="${circleRadius}" stroke-dasharray="${circumference}" stroke-dashoffset="${halfCircumference}" stroke-width="${thisStrokeWidth}"></circle></svg>`;
return html `<div class="layer"><div class="clipper left">${circle(strokeWidth)}</div><div class="gap-patch">${circle(strokeWidth * 0.8)}</div><div class="clipper right">${circle(strokeWidth)}</div></div>`;
}
};
CircularProgress.styles = [componentStyle, style];
__decorate([
property({ type: Number, reflect: true })
], CircularProgress.prototype, "max", void 0);
__decorate([
property({ type: Number })
], CircularProgress.prototype, "value", void 0);
CircularProgress = __decorate([
customElement('mdui-circular-progress')
], CircularProgress);
export { CircularProgress };

View File

@@ -0,0 +1 @@
export declare const style: import("lit").CSSResult;

View File

@@ -0,0 +1,2 @@
import { css } from 'lit';
export const style = css `:host{position:relative;display:inline-block;flex-shrink:0;width:2.5rem;height:2.5rem;stroke:rgb(var(--mdui-color-primary))}.progress{position:relative;display:inline-block;width:100%;height:100%;text-align:left;transition:opacity var(--mdui-motion-duration-medium1) var(--mdui-motion-easing-linear)}.determinate svg{transform:rotate(-90deg);fill:transparent}.determinate .track{stroke:transparent}.determinate .circle{stroke:inherit;transition:stroke-dashoffset var(--mdui-motion-duration-long2) var(--mdui-motion-easing-standard)}.indeterminate{font-size:0;letter-spacing:0;white-space:nowrap;animation:mdui-comp-circular-progress-rotate 1568ms var(--mdui-motion-easing-linear) infinite}.indeterminate .circle,.indeterminate .layer{position:absolute;width:100%;height:100%}.indeterminate .layer{animation:mdui-comp-circular-progress-layer-rotate 5332ms var(--mdui-motion-easing-standard) infinite both}.indeterminate .circle{fill:transparent;stroke:inherit}.indeterminate .gap-patch{position:absolute;top:0;left:47.5%;width:5%;height:100%;overflow:hidden}.indeterminate .gap-patch .circle{left:-900%;width:2000%;transform:rotate(180deg)}.indeterminate .clipper{position:relative;display:inline-block;width:50%;height:100%;overflow:hidden}.indeterminate .clipper .circle{width:200%}.indeterminate .clipper.left .circle{animation:mdui-comp-circular-progress-left-spin 1333ms var(--mdui-motion-easing-standard) infinite both}.indeterminate .clipper.right .circle{left:-100%;animation:mdui-comp-circular-progress-right-spin 1333ms var(--mdui-motion-easing-standard) infinite both}@keyframes mdui-comp-circular-progress-rotate{to{transform:rotate(360deg)}}@keyframes mdui-comp-circular-progress-layer-rotate{12.5%{transform:rotate(135deg)}25%{transform:rotate(270deg)}37.5%{transform:rotate(405deg)}50%{transform:rotate(540deg)}62.5%{transform:rotate(675deg)}75%{transform:rotate(810deg)}87.5%{transform:rotate(945deg)}100%{transform:rotate(1080deg)}}@keyframes mdui-comp-circular-progress-left-spin{0%{transform:rotate(265deg)}50%{transform:rotate(130deg)}100%{transform:rotate(265deg)}}@keyframes mdui-comp-circular-progress-right-spin{0%{transform:rotate(-265deg)}50%{transform:rotate(-130deg)}100%{transform:rotate(-265deg)}}`;

View File

@@ -0,0 +1 @@
export * from './collapse/collapse-item.js';

View File

@@ -0,0 +1 @@
export * from './collapse/collapse-item.js';

View File

@@ -0,0 +1 @@
export * from './collapse/collapse.js';

View File

@@ -0,0 +1 @@
export * from './collapse/collapse.js';

View File

@@ -0,0 +1 @@
export declare const collapseItemStyle: import("lit").CSSResult;

View File

@@ -0,0 +1,2 @@
import { css } from 'lit';
export const collapseItemStyle = css `:host{display:flex;flex-direction:column}.header{display:block}.body{display:block;overflow:hidden;transition:height var(--mdui-motion-duration-short4) var(--mdui-motion-easing-emphasized)}.body.opened{overflow:visible}.body.active{transition-duration:var(--mdui-motion-duration-medium4)}`;

View File

@@ -0,0 +1,68 @@
import '@mdui/jq/methods/height.js';
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import type { JQ } from '@mdui/jq/shared/core.js';
import type { CSSResultGroup, TemplateResult, PropertyValues } from 'lit';
/**
* @summary 折叠面板项组件,需配合 `<mdui-collapse>` 组件使用
*
* ```html
* <mdui-collapse>
* ..<mdui-collapse-item header="header-1">content-1</mdui-collapse-item>
* ..<mdui-collapse-item header="header-2">content-2</mdui-collapse-item>
* </mdui-collapse>
* ```
*
* @event open - 开始打开时,事件被触发
* @event opened - 打开动画完成时,事件被触发
* @event close - 开始关闭时,事件被触发
* @event closed - 关闭动画完成时,事件被触发
*
* @slot - 折叠面板项的正文内容
* @slot header - 折叠面板项的头部内容
*
* @csspart header - 折叠面板的头部内容
* @csspart body - 折叠面板的正文内容
*/
export declare class CollapseItem extends MduiElement<CollapseItemEventMap> {
static styles: CSSResultGroup;
/**
* 此折叠面板项的值
*/
value?: string;
/**
* 此折叠面板项的头部文本
*/
header?: string;
/**
* 是否禁用此折叠面板项
*/
disabled: boolean;
/**
* 点击该元素时触发折叠,值可以是 CSS 选择器、DOM 元素、或 [JQ 对象](/docs/2/functions/jq)。默认为点击整个 header 区域触发
*/
trigger?: string | HTMLElement | JQ<HTMLElement>;
/**
* 是否为激活状态,由 `collapse` 组件控制该参数
*/
protected active: boolean;
private state;
protected isInitial: boolean;
protected readonly key: number;
private readonly bodyRef;
private onActiveChange;
protected firstUpdated(changedProperties: PropertyValues): void;
protected render(): TemplateResult;
private onTransitionEnd;
private updateBodyHeight;
}
export interface CollapseItemEventMap {
open: CustomEvent<void>;
opened: CustomEvent<void>;
close: CustomEvent<void>;
closed: CustomEvent<void>;
}
declare global {
interface HTMLElementTagNameMap {
'mdui-collapse-item': CollapseItem;
}
}

View File

@@ -0,0 +1,130 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { createRef, ref } from 'lit/directives/ref.js';
import { $ } from '@mdui/jq/$.js';
import '@mdui/jq/methods/height.js';
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import { watch } from '@mdui/shared/decorators/watch.js';
import { booleanConverter } from '@mdui/shared/helpers/decorator.js';
import { uniqueId } from '@mdui/shared/helpers/uniqueId.js';
import { componentStyle } from '@mdui/shared/lit-styles/component-style.js';
import { collapseItemStyle } from './collapse-item-style.js';
/**
* @summary 折叠面板项组件,需配合 `<mdui-collapse>` 组件使用
*
* ```html
* <mdui-collapse>
* ..<mdui-collapse-item header="header-1">content-1</mdui-collapse-item>
* ..<mdui-collapse-item header="header-2">content-2</mdui-collapse-item>
* </mdui-collapse>
* ```
*
* @event open - 开始打开时,事件被触发
* @event opened - 打开动画完成时,事件被触发
* @event close - 开始关闭时,事件被触发
* @event closed - 关闭动画完成时,事件被触发
*
* @slot - 折叠面板项的正文内容
* @slot header - 折叠面板项的头部内容
*
* @csspart header - 折叠面板的头部内容
* @csspart body - 折叠面板的正文内容
*/
let CollapseItem = class CollapseItem extends MduiElement {
constructor() {
super(...arguments);
/**
* 是否禁用此折叠面板项
*/
this.disabled = false;
/**
* 是否为激活状态,由 `collapse` 组件控制该参数
*/
this.active = false;
this.state = 'closed';
// 是否是初始状态,不显示动画
this.isInitial = true;
// 每一个 `collapse-item` 元素都添加一个唯一的 key
this.key = uniqueId();
this.bodyRef = createRef();
}
onActiveChange() {
if (this.isInitial) {
this.state = this.active ? 'opened' : 'closed';
if (this.hasUpdated) {
this.updateBodyHeight();
}
}
else {
this.state = this.active ? 'open' : 'close';
this.emit(this.state);
this.updateBodyHeight();
}
}
firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
this.updateBodyHeight();
}
render() {
return html `<slot name="header" part="header" class="header">${this.header}</slot><slot part="body" class="body ${classMap({
opened: this.state === 'opened',
active: this.active,
})}" ${ref(this.bodyRef)} @transitionend="${this.onTransitionEnd}"></slot>`;
}
onTransitionEnd(event) {
if (event.target === this.bodyRef.value) {
this.state = this.active ? 'opened' : 'closed';
this.emit(this.state);
this.updateBodyHeight();
}
}
updateBodyHeight() {
const scrollHeight = this.bodyRef.value.scrollHeight;
// 如果是从 opened 状态开始关闭,则先设置高度值,并等重绘完成
if (this.state === 'close') {
$(this.bodyRef.value).height(scrollHeight);
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
this.bodyRef.value.clientLeft;
}
$(this.bodyRef.value).height(this.state === 'opened'
? 'auto'
: this.state === 'open'
? scrollHeight
: 0);
}
};
CollapseItem.styles = [
componentStyle,
collapseItemStyle,
];
__decorate([
property({ reflect: true })
], CollapseItem.prototype, "value", void 0);
__decorate([
property({ reflect: true })
], CollapseItem.prototype, "header", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], CollapseItem.prototype, "disabled", void 0);
__decorate([
property()
], CollapseItem.prototype, "trigger", void 0);
__decorate([
state()
], CollapseItem.prototype, "active", void 0);
__decorate([
state()
], CollapseItem.prototype, "state", void 0);
__decorate([
watch('active')
], CollapseItem.prototype, "onActiveChange", null);
CollapseItem = __decorate([
customElement('mdui-collapse-item')
], CollapseItem);
export { CollapseItem };

View File

@@ -0,0 +1 @@
export declare const collapseStyle: import("lit").CSSResult;

View File

@@ -0,0 +1,2 @@
import { css } from 'lit';
export const collapseStyle = css `:host{display:block}`;

View File

@@ -0,0 +1,54 @@
import '@mdui/jq/methods/is.js';
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import type { CSSResultGroup, TemplateResult } from 'lit';
/**
* @summary 折叠面板组件,需配合 `<mdui-collapse-item>` 组件使用
*
* ```html
* <mdui-collapse>
* ..<mdui-collapse-item header="header-1">content-1</mdui-collapse-item>
* ..<mdui-collapse-item header="header-2">content-2</mdui-collapse-item>
* </mdui-collapse>
* ```
*
* @event change - 当前展开的折叠面板项变化时触发
*
* @slot - `<mdui-collapse-item>` 组件
*/
export declare class Collapse extends MduiElement<CollapseEventMap> {
static styles: CSSResultGroup;
/**
* 是否启用手风琴模式
*/
accordion: boolean;
/**
* 当前展开的 `<mdui-collapse-item>` 的值
*
* **Note**:该属性的 HTML 属性始终为字符串,只有在 `accordion` 为 `true` 时,才能设置初始值;该属性的 JavaScript 属性值在 `accordion` 为 `true` 时为字符串,在 `accordion` 为 `false` 时为字符串数组。因此,当 `accordion` 为 `false` 时,只能通过修改 JavaScript 属性值来改变此值。
*/
value?: string | string[];
/**
* 是否禁用此折叠面板
*/
disabled: boolean;
private activeKeys;
private readonly items;
private isInitial;
private definedController;
private onActiveKeysChange;
private onValueChange;
protected render(): TemplateResult;
private setActiveKeys;
private setValue;
private onClick;
private onSlotChange;
private updateItems;
}
export interface CollapseEventMap {
change: CustomEvent<void>;
}
declare global {
interface HTMLElementTagNameMap {
'mdui-collapse': Collapse;
}
}

View File

@@ -0,0 +1,198 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property, queryAssignedElements, state, } from 'lit/decorators.js';
import { $ } from '@mdui/jq/$.js';
import '@mdui/jq/methods/is.js';
import { isElement, isUndefined } from '@mdui/jq/shared/helper.js';
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import { DefinedController } from '@mdui/shared/controllers/defined.js';
import { watch } from '@mdui/shared/decorators/watch.js';
import { arraysEqualIgnoreOrder } from '@mdui/shared/helpers/array.js';
import { booleanConverter } from '@mdui/shared/helpers/decorator.js';
import { componentStyle } from '@mdui/shared/lit-styles/component-style.js';
import { collapseStyle } from './collapse-style.js';
/**
* @summary 折叠面板组件,需配合 `<mdui-collapse-item>` 组件使用
*
* ```html
* <mdui-collapse>
* ..<mdui-collapse-item header="header-1">content-1</mdui-collapse-item>
* ..<mdui-collapse-item header="header-2">content-2</mdui-collapse-item>
* </mdui-collapse>
* ```
*
* @event change - 当前展开的折叠面板项变化时触发
*
* @slot - `<mdui-collapse-item>` 组件
*/
let Collapse = class Collapse extends MduiElement {
constructor() {
super(...arguments);
/**
* 是否启用手风琴模式
*/
this.accordion = false;
/**
* 是否禁用此折叠面板
*/
this.disabled = false;
// 因为 collapse-item 的 value 可能会重复,所以在每个 collapse-item 元素上都添加了一个唯一的 key通过 activeKey 来记录激活状态的 key
this.activeKeys = [];
// 是否是初始状态,初始状态不触发 change 事件,没有动画
this.isInitial = true;
this.definedController = new DefinedController(this, {
relatedElements: ['mdui-collapse-item'],
});
}
async onActiveKeysChange() {
await this.definedController.whenDefined();
// 根据 activeKeys 读取对应 collapse-item 的值
const value = this.accordion
? this.items.find((item) => this.activeKeys.includes(item.key))?.value
: this.items
.filter((item) => this.activeKeys.includes(item.key))
.map((item) => item.value);
this.setValue(value);
if (!this.isInitial) {
this.emit('change');
}
}
async onValueChange() {
this.isInitial = !this.hasUpdated;
await this.definedController.whenDefined();
if (this.accordion) {
const value = this.value;
if (!value) {
this.setActiveKeys([]);
}
else {
const item = this.items.find((item) => item.value === value);
this.setActiveKeys(item ? [item.key] : []);
}
}
else {
const value = this.value;
if (!value.length) {
this.setActiveKeys([]);
}
else {
const activeKeys = this.items
.filter((item) => value.includes(item.value))
.map((item) => item.key);
this.setActiveKeys(activeKeys);
}
}
this.updateItems();
}
render() {
return html `<slot @slotchange="${this.onSlotChange}" @click="${this.onClick}"></slot>`;
}
setActiveKeys(activeKeys) {
if (!arraysEqualIgnoreOrder(this.activeKeys, activeKeys)) {
this.activeKeys = activeKeys;
}
}
setValue(value) {
if (this.accordion || isUndefined(this.value) || isUndefined(value)) {
this.value = value;
}
else if (!arraysEqualIgnoreOrder(this.value, value)) {
this.value = value;
}
}
onClick(event) {
// 全部禁用
if (this.disabled) {
return;
}
// event.button 为 0 时,为鼠标左键点击。忽略鼠标中键和右键
if (event.button) {
return;
}
const target = event.target;
const item = target.closest('mdui-collapse-item');
// collapse-item 被禁用,忽略
if (!item || item.disabled) {
return;
}
const path = event.composedPath();
// 指定了 trigger 时,点击了其他地方时,忽略
if (item.trigger &&
!path.find((element) => isElement(element) && $(element).is(item.trigger))) {
return;
}
// header 元素,忽略点击 header 以外的元素
if (!path.find((element) => isElement(element) && element.part.contains('header'))) {
return;
}
if (this.accordion) {
if (this.activeKeys.includes(item.key)) {
this.setActiveKeys([]);
}
else {
this.setActiveKeys([item.key]);
}
}
else {
// 直接修改 this.activeKeys 无法被 watch 监听到,需要先克隆一份 this.activeKeys
const activeKeys = [...this.activeKeys];
if (activeKeys.includes(item.key)) {
activeKeys.splice(activeKeys.indexOf(item.key), 1);
}
else {
activeKeys.push(item.key);
}
this.setActiveKeys(activeKeys);
}
this.isInitial = false;
this.updateItems();
}
async onSlotChange() {
await this.definedController.whenDefined();
this.updateItems();
}
// 更新 <mdui-collapse-item> 的状态
updateItems() {
this.items.forEach((item) => {
item.active = this.activeKeys.includes(item.key);
item.isInitial = this.isInitial;
});
}
};
Collapse.styles = [
componentStyle,
collapseStyle,
];
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Collapse.prototype, "accordion", void 0);
__decorate([
property()
], Collapse.prototype, "value", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Collapse.prototype, "disabled", void 0);
__decorate([
state()
], Collapse.prototype, "activeKeys", void 0);
__decorate([
queryAssignedElements({ selector: 'mdui-collapse-item', flatten: true })
], Collapse.prototype, "items", void 0);
__decorate([
watch('activeKeys', true)
], Collapse.prototype, "onActiveKeysChange", null);
__decorate([
watch('value')
], Collapse.prototype, "onValueChange", null);
Collapse = __decorate([
customElement('mdui-collapse')
], Collapse);
export { Collapse };

View File

@@ -0,0 +1 @@
export * from './dialog/index.js';

View File

@@ -0,0 +1 @@
export * from './dialog/index.js';

View File

@@ -0,0 +1,107 @@
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import '../icon.js';
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
/**
* @summary 对话框组件
*
* ```html
* <mdui-dialog>content</mdui-dialog>
* ```
*
* @event open - 对话框开始打开时触发。可以通过调用 `event.preventDefault()` 阻止对话框打开
* @event opened - 对话框打开动画完成后触发
* @event close - 对话框开始关闭时触发。可以通过调用 `event.preventDefault()` 阻止对话框关闭
* @event closed - 对话框关闭动画完成后触发
* @event overlay-click - 点击遮罩层时触发
*
* @slot header - 顶部元素,默认包含 `icon` slot 和 `headline` slot
* @slot icon - 顶部图标
* @slot headline - 顶部标题
* @slot description - 标题下方的文本
* @slot - 对话框主体内容
* @slot action - 底部操作栏中的元素
*
* @csspart overlay - 遮罩层
* @csspart panel - 对话框容器
* @csspart header - 对话框 header 部分,包含 icon 和 headline
* @csspart icon - 顶部图标,位于 header 中
* @csspart headline - 顶部标题,位于 header 中
* @csspart body - 对话框 body 部分
* @csspart description - 副文本部分,位于 body 中
* @csspart action - 底部操作按钮
*
* @cssprop --shape-corner - 组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
* @cssprop --z-index - 组件的 CSS `z-index` 值
*/
export declare class Dialog extends MduiElement<DialogEventMap> {
static styles: CSSResultGroup;
/**
* 顶部的 Material Icons 图标名。也可以通过 `slot="icon"` 设置
*/
icon?: string;
/**
* 标题。也可以通过 `slot="headline"` 设置
*/
headline?: string;
/**
* 标题下方的文本。也可以通过 `slot="description"` 设置
*/
description?: string;
/**
* 是否打开对话框
*/
open: boolean;
/**
* 是否全屏显示对话框
*/
fullscreen: boolean;
/**
* 是否允许按下 ESC 键关闭对话框
*/
closeOnEsc: boolean;
/**
* 是否允许点击遮罩层关闭对话框
*/
closeOnOverlayClick: boolean;
/**
* 是否垂直排列底部操作按钮
*/
stackedActions: boolean;
/**
* 是否可拖拽移动位置
*/
/**
* 是否可拖拽改变大小
*/
/**
* dialog 组件内包含的 mdui-top-app-bar 组件
*/
private readonly topAppBarElements;
private originalTrigger;
private modalHelper;
private readonly overlayRef;
private readonly panelRef;
private readonly bodyRef;
private readonly hasSlotController;
private readonly definedController;
private onOpenChange;
disconnectedCallback(): void;
protected firstUpdated(_changedProperties: PropertyValues): void;
protected render(): TemplateResult;
private onOverlayClick;
private renderIcon;
private renderHeadline;
private renderDescription;
}
export interface DialogEventMap {
open: CustomEvent<void>;
opened: CustomEvent<void>;
close: CustomEvent<void>;
closed: CustomEvent<void>;
'overlay-click': CustomEvent<void>;
}
declare global {
interface HTMLElementTagNameMap {
'mdui-dialog': Dialog;
}
}

View File

@@ -0,0 +1,311 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property, queryAssignedElements, } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { createRef, ref } from 'lit/directives/ref.js';
import { when } from 'lit/directives/when.js';
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import { DefinedController } from '@mdui/shared/controllers/defined.js';
import { HasSlotController } from '@mdui/shared/controllers/has-slot.js';
import { watch } from '@mdui/shared/decorators/watch.js';
import { animateTo, stopAnimations } from '@mdui/shared/helpers/animate.js';
import { booleanConverter } from '@mdui/shared/helpers/decorator.js';
import { Modal } from '@mdui/shared/helpers/modal.js';
import { getDuration, getEasing } from '@mdui/shared/helpers/motion.js';
import { lockScreen, unlockScreen } from '@mdui/shared/helpers/scroll.js';
import { nothingTemplate } from '@mdui/shared/helpers/template.js';
import { componentStyle } from '@mdui/shared/lit-styles/component-style.js';
import { offLocaleReady } from '../../internal/localize.js';
import '../icon.js';
import { style } from './style.js';
/**
* @summary 对话框组件
*
* ```html
* <mdui-dialog>content</mdui-dialog>
* ```
*
* @event open - 对话框开始打开时触发。可以通过调用 `event.preventDefault()` 阻止对话框打开
* @event opened - 对话框打开动画完成后触发
* @event close - 对话框开始关闭时触发。可以通过调用 `event.preventDefault()` 阻止对话框关闭
* @event closed - 对话框关闭动画完成后触发
* @event overlay-click - 点击遮罩层时触发
*
* @slot header - 顶部元素,默认包含 `icon` slot 和 `headline` slot
* @slot icon - 顶部图标
* @slot headline - 顶部标题
* @slot description - 标题下方的文本
* @slot - 对话框主体内容
* @slot action - 底部操作栏中的元素
*
* @csspart overlay - 遮罩层
* @csspart panel - 对话框容器
* @csspart header - 对话框 header 部分,包含 icon 和 headline
* @csspart icon - 顶部图标,位于 header 中
* @csspart headline - 顶部标题,位于 header 中
* @csspart body - 对话框 body 部分
* @csspart description - 副文本部分,位于 body 中
* @csspart action - 底部操作按钮
*
* @cssprop --shape-corner - 组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
* @cssprop --z-index - 组件的 CSS `z-index` 值
*/
let Dialog = class Dialog extends MduiElement {
constructor() {
super(...arguments);
/**
* 是否打开对话框
*/
this.open = false;
/**
* 是否全屏显示对话框
*/
this.fullscreen = false;
/**
* 是否允许按下 ESC 键关闭对话框
*/
this.closeOnEsc = false;
/**
* 是否允许点击遮罩层关闭对话框
*/
this.closeOnOverlayClick = false;
/**
* 是否垂直排列底部操作按钮
*/
this.stackedActions = false;
this.overlayRef = createRef();
this.panelRef = createRef();
this.bodyRef = createRef();
this.hasSlotController = new HasSlotController(this, 'header', 'icon', 'headline', 'description', 'action', '[default]');
this.definedController = new DefinedController(this, {
relatedElements: ['mdui-top-app-bar'],
});
}
async onOpenChange() {
const hasUpdated = this.hasUpdated;
// 默认为关闭状态。因此首次渲染时,且为关闭状态,不执行
if (!this.open && !hasUpdated) {
return;
}
await this.definedController.whenDefined();
if (!hasUpdated) {
await this.updateComplete;
}
// 内部的 header, body, actions 元素
const children = Array.from(this.panelRef.value.querySelectorAll('.header, .body, .actions'));
const easingLinear = getEasing(this, 'linear');
const easingEmphasizedDecelerate = getEasing(this, 'emphasized-decelerate');
const easingEmphasizedAccelerate = getEasing(this, 'emphasized-accelerate');
const stopAnimation = () => Promise.all([
stopAnimations(this.overlayRef.value),
stopAnimations(this.panelRef.value),
...children.map((child) => stopAnimations(child)),
]);
// 打开
// 要区分是否首次渲染,首次渲染不触发事件,不执行动画;非首次渲染,触发事件,执行动画
if (this.open) {
if (hasUpdated) {
const eventProceeded = this.emit('open', { cancelable: true });
if (!eventProceeded) {
return;
}
}
this.style.display = 'flex';
// 包含 <mdui-top-app-bar slot="header"> 时
const topAppBarElements = this.topAppBarElements ?? [];
if (topAppBarElements.length) {
const topAppBarElement = topAppBarElements[0];
// top-app-bar 未设置 scrollTarget 时,默认设置为 bodyRef
if (!topAppBarElement.scrollTarget) {
topAppBarElement.scrollTarget = this.bodyRef.value;
}
// 移除 header 和 body 之间的 margin
this.bodyRef.value.style.marginTop = '0';
}
this.originalTrigger = document.activeElement;
this.modalHelper.activate();
lockScreen(this);
await stopAnimation();
// 设置聚焦
requestAnimationFrame(() => {
const autoFocusTarget = this.querySelector('[autofocus]');
if (autoFocusTarget) {
autoFocusTarget.focus({ preventScroll: true });
}
else {
this.panelRef.value.focus({ preventScroll: true });
}
});
const duration = getDuration(this, 'medium4');
await Promise.all([
animateTo(this.overlayRef.value, [{ opacity: 0 }, { opacity: 1, offset: 0.3 }, { opacity: 1 }], {
duration: hasUpdated ? duration : 0,
easing: easingLinear,
}),
animateTo(this.panelRef.value, [
{ transform: 'translateY(-1.875rem) scaleY(0)' },
{ transform: 'translateY(0) scaleY(1)' },
], {
duration: hasUpdated ? duration : 0,
easing: easingEmphasizedDecelerate,
}),
animateTo(this.panelRef.value, [{ opacity: 0 }, { opacity: 1, offset: 0.1 }, { opacity: 1 }], {
duration: hasUpdated ? duration : 0,
easing: easingLinear,
}),
...children.map((child) => animateTo(child, [
{ opacity: 0 },
{ opacity: 0, offset: 0.2 },
{ opacity: 1, offset: 0.8 },
{ opacity: 1 },
], {
duration: hasUpdated ? duration : 0,
easing: easingLinear,
})),
]);
if (hasUpdated) {
this.emit('opened');
}
}
else {
const eventProceeded = this.emit('close', { cancelable: true });
if (!eventProceeded) {
return;
}
this.modalHelper.deactivate();
await stopAnimation();
const duration = getDuration(this, 'short4');
await Promise.all([
animateTo(this.overlayRef.value, [{ opacity: 1 }, { opacity: 0 }], {
duration,
easing: easingLinear,
}),
animateTo(this.panelRef.value, [
{ transform: 'translateY(0) scaleY(1)' },
{ transform: 'translateY(-1.875rem) scaleY(0.6)' },
], { duration, easing: easingEmphasizedAccelerate }),
animateTo(this.panelRef.value, [{ opacity: 1 }, { opacity: 1, offset: 0.75 }, { opacity: 0 }], { duration, easing: easingLinear }),
...children.map((child) => animateTo(child, [{ opacity: 1 }, { opacity: 0, offset: 0.75 }, { opacity: 0 }], { duration, easing: easingLinear })),
]);
this.style.display = 'none';
unlockScreen(this);
// 对话框关闭后,恢复焦点到原有的元素上
const trigger = this.originalTrigger;
if (typeof trigger?.focus === 'function') {
setTimeout(() => trigger.focus());
}
this.emit('closed');
}
}
disconnectedCallback() {
super.disconnectedCallback();
unlockScreen(this);
// alert, confirm, prompt 函数支持 localize。这里确保在组件销毁时取消监听 localize ready 事件
offLocaleReady(this);
}
firstUpdated(_changedProperties) {
super.firstUpdated(_changedProperties);
this.modalHelper = new Modal(this);
this.addEventListener('keydown', (event) => {
if (this.open && this.closeOnEsc && event.key === 'Escape') {
event.stopPropagation();
this.open = false;
}
});
}
render() {
const hasActionSlot = this.hasSlotController.test('action');
const hasDefaultSlot = this.hasSlotController.test('[default]');
const hasIcon = !!this.icon || this.hasSlotController.test('icon');
const hasHeadline = !!this.headline || this.hasSlotController.test('headline');
const hasDescription = !!this.description || this.hasSlotController.test('description');
const hasHeader = hasIcon || hasHeadline || this.hasSlotController.test('header');
const hasBody = hasDescription || hasDefaultSlot;
// modify: 移除了 tabindex="0", 换为 tabindex
return html `<div ${ref(this.overlayRef)} part="overlay" class="overlay" @click="${this.onOverlayClick}" tabindex="-1"></div><div ${ref(this.panelRef)} part="panel" class="panel ${classMap({
'has-icon': hasIcon,
'has-description': hasDescription,
'has-default': hasDefaultSlot,
})}" tabindex>${when(hasHeader, () => html `<slot name="header" part="header" class="header">${when(hasIcon, () => this.renderIcon())} ${when(hasHeadline, () => this.renderHeadline())}</slot>`)} ${when(hasBody, () => html `<div ${ref(this.bodyRef)} part="body" class="body">${when(hasDescription, () => this.renderDescription())}<slot></slot></div>`)} ${when(hasActionSlot, () => html `<slot name="action" part="action" class="action"></slot>`)}</div>`;
}
onOverlayClick() {
this.emit('overlay-click');
if (!this.closeOnOverlayClick) {
return;
}
this.open = false;
}
renderIcon() {
return html `<slot name="icon" part="icon" class="icon">${this.icon
? html `<mdui-icon name="${this.icon}"></mdui-icon>`
: nothingTemplate}</slot>`;
}
renderHeadline() {
return html `<slot name="headline" part="headline" class="headline">${this.headline}</slot>`;
}
renderDescription() {
return html `<slot name="description" part="description" class="description">${this.description}</slot>`;
}
};
Dialog.styles = [componentStyle, style];
__decorate([
property({ reflect: true })
], Dialog.prototype, "icon", void 0);
__decorate([
property({ reflect: true })
], Dialog.prototype, "headline", void 0);
__decorate([
property({ reflect: true })
], Dialog.prototype, "description", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Dialog.prototype, "open", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Dialog.prototype, "fullscreen", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
attribute: 'close-on-esc',
})
], Dialog.prototype, "closeOnEsc", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
attribute: 'close-on-overlay-click',
})
], Dialog.prototype, "closeOnOverlayClick", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
attribute: 'stacked-actions',
})
], Dialog.prototype, "stackedActions", void 0);
__decorate([
queryAssignedElements({
slot: 'header',
selector: 'mdui-top-app-bar',
flatten: true,
})
], Dialog.prototype, "topAppBarElements", void 0);
__decorate([
watch('open')
], Dialog.prototype, "onOpenChange", null);
Dialog = __decorate([
customElement('mdui-dialog')
], Dialog);
export { Dialog };

View File

@@ -0,0 +1 @@
export declare const style: import("lit").CSSResult;

View File

@@ -0,0 +1,2 @@
import { css } from 'lit';
export const style = css `:host{--shape-corner:var(--mdui-shape-corner-extra-large);--z-index:2300;position:fixed;z-index:var(--z-index);display:none;align-items:center;justify-content:center;inset:0;padding:3rem}::slotted(mdui-top-app-bar[slot=header]){position:absolute;border-top-left-radius:var(--mdui-shape-corner-extra-large);border-top-right-radius:var(--mdui-shape-corner-extra-large);background-color:rgb(var(--mdui-color-surface-container-high))}:host([fullscreen]:not([fullscreen=false i])){--shape-corner:var(--mdui-shape-corner-none);padding:0}:host([fullscreen]:not([fullscreen=false i])) ::slotted(mdui-top-app-bar[slot=header]){border-top-left-radius:var(--mdui-shape-corner-none);border-top-right-radius:var(--mdui-shape-corner-none)}.overlay{position:fixed;inset:0;background-color:rgba(var(--mdui-color-scrim),.4)}.panel{--mdui-color-background:var(--mdui-color-surface-container-high);position:relative;display:flex;flex-direction:column;max-height:100%;border-radius:var(--shape-corner);outline:0;transform-origin:top;min-width:17.5rem;max-width:35rem;padding:1.5rem;background-color:rgb(var(--mdui-color-surface-container-high));box-shadow:var(--mdui-elevation-level3)}:host([fullscreen]:not([fullscreen=false i])) .panel{width:100%;max-width:100%;height:100%;max-height:100%;box-shadow:var(--mdui-elevation-level0)}.header{display:flex;flex-direction:column}.has-icon .header{align-items:center}.icon{display:flex;color:rgb(var(--mdui-color-secondary));font-size:1.5rem}.icon mdui-icon,::slotted([slot=icon]){font-size:inherit}.headline{display:flex;color:rgb(var(--mdui-color-on-surface));font-size:var(--mdui-typescale-headline-small-size);font-weight:var(--mdui-typescale-headline-small-weight);letter-spacing:var(--mdui-typescale-headline-small-tracking);line-height:var(--mdui-typescale-headline-small-line-height)}.icon+.headline{padding-top:1rem}.body{overflow:auto}.header+.body{margin-top:1rem}.description{display:flex;color:rgb(var(--mdui-color-on-surface-variant));font-size:var(--mdui-typescale-body-medium-size);font-weight:var(--mdui-typescale-body-medium-weight);letter-spacing:var(--mdui-typescale-body-medium-tracking);line-height:var(--mdui-typescale-body-medium-line-height)}:host([fullscreen]:not([fullscreen=false i])) .description{color:rgb(var(--mdui-color-on-surface))}.has-description.has-default .description{margin-bottom:1rem}.action{display:flex;justify-content:flex-end;padding-top:1.5rem}.action::slotted(:not(:first-child)){margin-left:.5rem}:host([stacked-actions]:not([stacked-actions=false i])) .action{flex-direction:column;align-items:end}:host([stacked-actions]:not([stacked-actions=false i])) .action::slotted(:not(:first-child)){margin-left:0;margin-top:.5rem}`;

View File

@@ -0,0 +1 @@
export * from './divider/index.js';

View File

@@ -0,0 +1 @@
export * from './divider/index.js';

View File

@@ -0,0 +1,32 @@
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import type { CSSResultGroup, TemplateResult } from 'lit';
/**
* @summary 分割线组件
*
* ```html
* <mdui-divider></mdui-divider>
* ```
*/
export declare class Divider extends MduiElement<DividerEventMap> {
static styles: CSSResultGroup;
/**
* 是否为垂直分割线
*/
vertical: boolean;
/**
* 是否进行左侧缩进
*/
inset: boolean;
/**
* 是否进行左右两侧缩进
*/
middle: boolean;
protected render(): TemplateResult;
}
export interface DividerEventMap {
}
declare global {
interface HTMLElementTagNameMap {
'mdui-divider': Divider;
}
}

View File

@@ -0,0 +1,60 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import { booleanConverter } from '@mdui/shared/helpers/decorator.js';
import { componentStyle } from '@mdui/shared/lit-styles/component-style.js';
import { style } from './style.js';
/**
* @summary 分割线组件
*
* ```html
* <mdui-divider></mdui-divider>
* ```
*/
let Divider = class Divider extends MduiElement {
constructor() {
super(...arguments);
/**
* 是否为垂直分割线
*/
this.vertical = false;
/**
* 是否进行左侧缩进
*/
this.inset = false;
/**
* 是否进行左右两侧缩进
*/
this.middle = false;
}
render() {
return html ``;
}
};
Divider.styles = [componentStyle, style];
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Divider.prototype, "vertical", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Divider.prototype, "inset", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Divider.prototype, "middle", void 0);
Divider = __decorate([
customElement('mdui-divider')
], Divider);
export { Divider };

View File

@@ -0,0 +1 @@
export declare const style: import("lit").CSSResult;

View File

@@ -0,0 +1,2 @@
import { css } from 'lit';
export const style = css `:host{display:block;height:.0625rem;background-color:rgb(var(--mdui-color-surface-variant))}:host([inset]:not([inset=false i])){margin-left:1rem}:host([middle]:not([middle=false i])){margin-left:1rem;margin-right:1rem}:host([vertical]:not([vertical=false i])){height:100%;width:.0625rem}`;

View File

@@ -0,0 +1 @@
export * from './dropdown/index.js';

View File

@@ -0,0 +1 @@
export * from './dropdown/index.js';

View File

@@ -0,0 +1,137 @@
import '@mdui/jq/methods/height.js';
import '@mdui/jq/methods/is.js';
import '@mdui/jq/methods/width.js';
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import type { CSSResultGroup, PropertyValues, TemplateResult } from 'lit';
/**
* @summary 下拉组件
*
* ```html
* <mdui-dropdown>
* ..<mdui-button slot="trigger">open dropdown</mdui-button>
* ..<mdui-menu>
* ....<mdui-menu-item>Item 1</mdui-menu-item>
* ....<mdui-menu-item>Item 2</mdui-menu-item>
* ..</mdui-menu>
* </mdui-dropdown>
* ```
*
* @event open - 下拉组件开始打开时,事件被触发。可以通过调用 `event.preventDefault()` 阻止下拉组件打开
* @event opened - 下拉组件打开动画完成时,事件被触发
* @event close - 下拉组件开始关闭时,事件被触发。可以通过调用 `event.preventDefault()` 阻止下拉组件关闭
* @event closed - 下拉组件关闭动画完成时,事件被触发
*
* @slot - 下拉组件的内容
* @slot trigger - 触发下拉组件的元素,例如 [`<mdui-button>`](/docs/2/components/button) 元素
*
* @csspart trigger - 触发下拉组件的元素的容器,即 `trigger` slot 的容器
* @csspart panel - 下拉组件内容的容器
*
* @cssprop --z-index - 组件的 CSS `z-index` 值
*/
export declare class Dropdown extends MduiElement<DropdownEventMap> {
static styles: CSSResultGroup;
/**
* 是否打开下拉组件
*/
open: boolean;
/**
* 是否禁用下拉组件
*/
disabled: boolean;
/**
* 下拉组件的触发方式,支持多个值,用空格分隔。可选值包括:
*
* * `click`:点击触发
* * `hover`:鼠标悬浮触发
* * `focus`:聚焦触发
* * `contextmenu`:鼠标右键点击、或触摸长按触发
* * `manual`:仅能通过编程方式打开和关闭下拉组件,不能再指定其他触发方式
*/
trigger: /*点击触发*/ 'click' | /*鼠标悬浮触发*/ 'hover' | /*聚焦触发*/ 'focus' | /*鼠标右键点击、或触摸长按触发*/ 'contextmenu' | /*仅能通过编程方式打开和关闭下拉组件,不能再指定其他触发方式*/ 'manual' | string;
/**
* 下拉组件内容的位置。可选值包括:
*
* * `auto`:自动判断位置
* * `top-start`:上方左对齐
* * `top`:上方居中
* * `top-end`:上方右对齐
* * `bottom-start`:下方左对齐
* * `bottom`:下方居中
* * `bottom-end`:下方右对齐
* * `left-start`:左侧顶部对齐
* * `left`:左侧居中
* * `left-end`:左侧底部对齐
* * `right-start`:右侧顶部对齐
* * `right`:右侧居中
* * `right-end`:右侧底部对齐
*/
placement: /*自动判断位置*/ 'auto' | /*上方左对齐*/ 'top-start' | /*上方居中*/ 'top' | /*上方右对齐*/ 'top-end' | /*下方左对齐*/ 'bottom-start' | /*下方居中*/ 'bottom' | /*下方右对齐*/ 'bottom-end' | /*左侧顶部对齐*/ 'left-start' | /*左侧居中*/ 'left' | /*左侧底部对齐*/ 'left-end' | /*右侧顶部对齐*/ 'right-start' | /*右侧居中*/ 'right' | /*右侧底部对齐*/ 'right-end';
/**
* 点击 [`<mdui-menu-item>`](/docs/2/components/menu#menu-item-api) 后,下拉组件是否保持打开状态
*/
stayOpenOnClick: boolean;
/**
* 鼠标悬浮触发下拉组件打开的延时,单位为毫秒
*/
openDelay: number;
/**
* 鼠标悬浮触发下拉组件关闭的延时,单位为毫秒
*/
closeDelay: number;
/**
* 是否在触发下拉组件的光标位置打开下拉组件,常用于打开鼠标右键菜单
*/
openOnPointer: boolean;
private readonly triggerElements;
private readonly panelElements;
private pointerOffsetX;
private pointerOffsetY;
private animateDirection;
private openTimeout;
private closeTimeout;
private observeResize?;
private overflowAncestors?;
private readonly panelRef;
private readonly definedController;
constructor();
private get triggerElement();
private onPositionChange;
private onOpenChange;
connectedCallback(): void;
disconnectedCallback(): void;
protected firstUpdated(changedProperties: PropertyValues): void;
protected render(): TemplateResult;
/**
* 获取 dropdown 打开、关闭动画的 CSS scaleX 或 scaleY
*/
private getCssScaleName;
/**
* 在 document 上点击时,根据条件判断是否要关闭 dropdown
*/
private onDocumentClick;
/**
* 在 document 上按下按键时,根据条件判断是否要关闭 dropdown
*/
private onDocumentKeydown;
private onWindowScroll;
private hasTrigger;
private onFocus;
private onClick;
private onPanelClick;
private onContextMenu;
private onMouseEnter;
private onMouseLeave;
private updatePositioner;
}
export interface DropdownEventMap {
open: CustomEvent<void>;
opened: CustomEvent<void>;
close: CustomEvent<void>;
closed: CustomEvent<void>;
}
declare global {
interface HTMLElementTagNameMap {
'mdui-dropdown': Dropdown;
}
}

View File

@@ -0,0 +1,583 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property, queryAssignedElements, } from 'lit/decorators.js';
import { createRef, ref } from 'lit/directives/ref.js';
import { getOverflowAncestors } from '@floating-ui/utils/dom';
import { $ } from '@mdui/jq/$.js';
import '@mdui/jq/methods/height.js';
import '@mdui/jq/methods/is.js';
import '@mdui/jq/methods/width.js';
import { isFunction } from '@mdui/jq/shared/helper.js';
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import { DefinedController } from '@mdui/shared/controllers/defined.js';
import { watch } from '@mdui/shared/decorators/watch.js';
import { animateTo, stopAnimations } from '@mdui/shared/helpers/animate.js';
import { booleanConverter } from '@mdui/shared/helpers/decorator.js';
import { getDuration, getEasing } from '@mdui/shared/helpers/motion.js';
import { observeResize } from '@mdui/shared/helpers/observeResize.js';
import { componentStyle } from '@mdui/shared/lit-styles/component-style.js';
import { style } from './style.js';
/**
* @summary 下拉组件
*
* ```html
* <mdui-dropdown>
* ..<mdui-button slot="trigger">open dropdown</mdui-button>
* ..<mdui-menu>
* ....<mdui-menu-item>Item 1</mdui-menu-item>
* ....<mdui-menu-item>Item 2</mdui-menu-item>
* ..</mdui-menu>
* </mdui-dropdown>
* ```
*
* @event open - 下拉组件开始打开时,事件被触发。可以通过调用 `event.preventDefault()` 阻止下拉组件打开
* @event opened - 下拉组件打开动画完成时,事件被触发
* @event close - 下拉组件开始关闭时,事件被触发。可以通过调用 `event.preventDefault()` 阻止下拉组件关闭
* @event closed - 下拉组件关闭动画完成时,事件被触发
*
* @slot - 下拉组件的内容
* @slot trigger - 触发下拉组件的元素,例如 [`<mdui-button>`](/docs/2/components/button) 元素
*
* @csspart trigger - 触发下拉组件的元素的容器,即 `trigger` slot 的容器
* @csspart panel - 下拉组件内容的容器
*
* @cssprop --z-index - 组件的 CSS `z-index` 值
*/
let Dropdown = class Dropdown extends MduiElement {
constructor() {
super();
/**
* 是否打开下拉组件
*/
this.open = false;
/**
* 是否禁用下拉组件
*/
this.disabled = false;
/**
* 下拉组件的触发方式,支持多个值,用空格分隔。可选值包括:
*
* * `click`:点击触发
* * `hover`:鼠标悬浮触发
* * `focus`:聚焦触发
* * `contextmenu`:鼠标右键点击、或触摸长按触发
* * `manual`:仅能通过编程方式打开和关闭下拉组件,不能再指定其他触发方式
*/
this.trigger = 'click';
/**
* 下拉组件内容的位置。可选值包括:
*
* * `auto`:自动判断位置
* * `top-start`:上方左对齐
* * `top`:上方居中
* * `top-end`:上方右对齐
* * `bottom-start`:下方左对齐
* * `bottom`:下方居中
* * `bottom-end`:下方右对齐
* * `left-start`:左侧顶部对齐
* * `left`:左侧居中
* * `left-end`:左侧底部对齐
* * `right-start`:右侧顶部对齐
* * `right`:右侧居中
* * `right-end`:右侧底部对齐
*/
this.placement = 'auto';
/**
* 点击 [`<mdui-menu-item>`](/docs/2/components/menu#menu-item-api) 后,下拉组件是否保持打开状态
*/
this.stayOpenOnClick = false;
/**
* 鼠标悬浮触发下拉组件打开的延时,单位为毫秒
*/
this.openDelay = 150;
/**
* 鼠标悬浮触发下拉组件关闭的延时,单位为毫秒
*/
this.closeDelay = 150;
/**
* 是否在触发下拉组件的光标位置打开下拉组件,常用于打开鼠标右键菜单
*/
this.openOnPointer = false;
this.panelRef = createRef();
this.definedController = new DefinedController(this, {
relatedElements: [''],
});
this.onDocumentClick = this.onDocumentClick.bind(this);
this.onDocumentKeydown = this.onDocumentKeydown.bind(this);
this.onWindowScroll = this.onWindowScroll.bind(this);
this.onMouseLeave = this.onMouseLeave.bind(this);
this.onFocus = this.onFocus.bind(this);
this.onClick = this.onClick.bind(this);
this.onContextMenu = this.onContextMenu.bind(this);
this.onMouseEnter = this.onMouseEnter.bind(this);
this.onPanelClick = this.onPanelClick.bind(this);
}
get triggerElement() {
return this.triggerElements[0];
}
// 这些属性变更时,需要更新样式
async onPositionChange() {
// 如果是打开状态,则更新 panel 的位置
if (this.open) {
await this.definedController.whenDefined();
this.updatePositioner();
}
}
async onOpenChange() {
const hasUpdated = this.hasUpdated;
// 默认为关闭状态。因此首次渲染时,且为关闭状态,不执行
if (!this.open && !hasUpdated) {
return;
}
await this.definedController.whenDefined();
if (!hasUpdated) {
await this.updateComplete;
}
const easingLinear = getEasing(this, 'linear');
const easingEmphasizedDecelerate = getEasing(this, 'emphasized-decelerate');
const easingEmphasizedAccelerate = getEasing(this, 'emphasized-accelerate');
// 打开
// 要区分是否首次渲染,首次渲染时不触发事件,不执行动画;非首次渲染,触发事件,执行动画
if (this.open) {
if (hasUpdated) {
const eventProceeded = this.emit('open', { cancelable: true });
if (!eventProceeded) {
return;
}
}
// dropdown 打开时,尝试把焦点放到 panel 中
const focusablePanel = this.panelElements.find((panel) => isFunction(panel.focus));
setTimeout(() => {
focusablePanel?.focus();
});
const duration = getDuration(this, 'medium4');
await stopAnimations(this.panelRef.value);
this.panelRef.value.hidden = false;
this.updatePositioner();
await Promise.all([
animateTo(this.panelRef.value, [
{ transform: `${this.getCssScaleName()}(0.45)` },
{ transform: `${this.getCssScaleName()}(1)` },
], {
duration: hasUpdated ? duration : 0,
easing: easingEmphasizedDecelerate,
}),
animateTo(this.panelRef.value, [{ opacity: 0 }, { opacity: 1, offset: 0.125 }, { opacity: 1 }], {
duration: hasUpdated ? duration : 0,
easing: easingLinear,
}),
]);
if (hasUpdated) {
this.emit('opened');
}
}
else {
const eventProceeded = this.emit('close', { cancelable: true });
if (!eventProceeded) {
return;
}
// dropdown 关闭时,如果不支持 focus 触发,且焦点在 dropdown 内,则焦点回到 trigger 上
if (!this.hasTrigger('focus') &&
isFunction(this.triggerElement?.focus) &&
(this.contains(document.activeElement) ||
this.contains(document.activeElement?.assignedSlot ?? null))) {
this.triggerElement.focus();
}
const duration = getDuration(this, 'short4');
await stopAnimations(this.panelRef.value);
await Promise.all([
animateTo(this.panelRef.value, [
{ transform: `${this.getCssScaleName()}(1)` },
{ transform: `${this.getCssScaleName()}(0.45)` },
], { duration, easing: easingEmphasizedAccelerate }),
animateTo(this.panelRef.value, [{ opacity: 1 }, { opacity: 1, offset: 0.875 }, { opacity: 0 }], { duration, easing: easingLinear }),
]);
// 可能关闭 dropdown 时该元素已经不存在了(比如页面直接跳转了)
if (this.panelRef.value) {
this.panelRef.value.hidden = true;
}
this.emit('closed');
}
}
connectedCallback() {
super.connectedCallback();
this.definedController.whenDefined().then(() => {
document.addEventListener('pointerdown', this.onDocumentClick);
document.addEventListener('keydown', this.onDocumentKeydown);
this.overflowAncestors = getOverflowAncestors(this.triggerElement);
this.overflowAncestors.forEach((ancestor) => {
ancestor.addEventListener('scroll', this.onWindowScroll);
});
// triggerElement 的尺寸变化时,重新调整 panel 的位置
this.observeResize = observeResize(this.triggerElement, () => {
this.updatePositioner();
});
});
}
disconnectedCallback() {
// 移除组件时,如果关闭动画正在进行中,则会导致关闭动画无法执行完成,最终组件无法隐藏
// 具体场景为 vue 的 <keep-alive> 中切换走,再切换回来时,面板仍然打开着
if (!this.open && this.panelRef.value) {
this.panelRef.value.hidden = true;
}
super.disconnectedCallback();
document.removeEventListener('pointerdown', this.onDocumentClick);
document.removeEventListener('keydown', this.onDocumentKeydown);
this.overflowAncestors?.forEach((ancestor) => {
ancestor.removeEventListener('scroll', this.onWindowScroll);
});
this.observeResize?.unobserve();
}
firstUpdated(changedProperties) {
super.firstUpdated(changedProperties);
this.addEventListener('mouseleave', this.onMouseLeave);
this.definedController.whenDefined().then(() => {
this.triggerElement.addEventListener('focus', this.onFocus);
this.triggerElement.addEventListener('click', this.onClick);
this.triggerElement.addEventListener('contextmenu', this.onContextMenu);
this.triggerElement.addEventListener('mouseenter', this.onMouseEnter);
});
}
render() {
return html `<slot name="trigger" part="trigger" class="trigger"></slot><slot ${ref(this.panelRef)} part="panel" class="panel" hidden @click="${this.onPanelClick}"></slot>`;
}
/**
* 获取 dropdown 打开、关闭动画的 CSS scaleX 或 scaleY
*/
getCssScaleName() {
return this.animateDirection === 'horizontal' ? 'scaleX' : 'scaleY';
}
/**
* 在 document 上点击时,根据条件判断是否要关闭 dropdown
*/
onDocumentClick(e) {
if (this.disabled || !this.open) {
return;
}
const path = e.composedPath();
// 点击 dropdown 外部区域,直接关闭
if (!path.includes(this)) {
this.open = false;
}
// 当包含 contextmenu 且不包含 click 时,点击 trigger关闭
if (this.hasTrigger('contextmenu') &&
!this.hasTrigger('click') &&
path.includes(this.triggerElement)) {
this.open = false;
}
}
/**
* 在 document 上按下按键时,根据条件判断是否要关闭 dropdown
*/
onDocumentKeydown(event) {
if (this.disabled || !this.open) {
return;
}
// 按下 ESC 键时,关闭 dropdown
if (event.key === 'Escape') {
this.open = false;
return;
}
// 按下 Tab 键时,关闭 dropdown
if (event.key === 'Tab') {
// 如果不支持 focus 触发,则焦点回到 trigger 上(这个会在 onOpenChange 中执行 )这里只需阻止默认的 Tab 行为
if (!this.hasTrigger('focus') && isFunction(this.triggerElement?.focus)) {
event.preventDefault();
}
this.open = false;
}
}
onWindowScroll() {
window.requestAnimationFrame(() => this.onPositionChange());
}
hasTrigger(trigger) {
const triggers = this.trigger.split(' ');
return triggers.includes(trigger);
}
onFocus() {
if (this.disabled || this.open || !this.hasTrigger('focus')) {
return;
}
this.open = true;
}
onClick(e) {
// e.button 为 0 时,为鼠标左键点击。忽略鼠标中间和右键
if (this.disabled || e.button || !this.hasTrigger('click')) {
return;
}
// 支持 hover 或 focus 触发时,点击时,不关闭 dropdown
if (this.open && (this.hasTrigger('hover') || this.hasTrigger('focus'))) {
return;
}
this.pointerOffsetX = e.offsetX;
this.pointerOffsetY = e.offsetY;
this.open = !this.open;
}
onPanelClick(e) {
if (!this.disabled &&
!this.stayOpenOnClick &&
$(e.target).is('mdui-menu-item')) {
this.open = false;
}
}
onContextMenu(e) {
if (this.disabled || !this.hasTrigger('contextmenu')) {
return;
}
e.preventDefault();
this.pointerOffsetX = e.offsetX;
this.pointerOffsetY = e.offsetY;
this.open = true;
}
onMouseEnter() {
// 不做 open 状态的判断,因为可以延时打开和关闭
if (this.disabled || !this.hasTrigger('hover')) {
return;
}
window.clearTimeout(this.closeTimeout);
if (this.openDelay) {
this.openTimeout = window.setTimeout(() => {
this.open = true;
}, this.openDelay);
}
else {
this.open = true;
}
}
onMouseLeave() {
// 不做 open 状态的判断,因为可以延时打开和关闭
if (this.disabled || !this.hasTrigger('hover')) {
return;
}
window.clearTimeout(this.openTimeout);
this.closeTimeout = window.setTimeout(() => {
this.open = false;
}, this.closeDelay || 50);
}
// 更新 panel 的位置
updatePositioner() {
const $panel = $(this.panelRef.value);
const $window = $(window);
const panelElements = this.panelElements;
const panelRect = {
width: Math.max(...(panelElements?.map((panel) => panel.offsetWidth) ?? [])),
height: panelElements
?.map((panel) => panel.offsetHeight)
.reduce((total, height) => total + height, 0),
};
// 在光标位置触发时,假设 triggerElement 的宽高为 0位置位于光标位置
const triggerClientRect = this.triggerElement.getBoundingClientRect();
const triggerRect = this.openOnPointer
? {
top: this.pointerOffsetY + triggerClientRect.top,
left: this.pointerOffsetX + triggerClientRect.left,
width: 0,
height: 0,
}
: triggerClientRect;
// dropdown 与屏幕边界至少保留 8px 间距
const screenMargin = 8;
let transformOriginX;
let transformOriginY;
let top;
let left;
let placement = this.placement;
// 自动判断 dropdown 的方位
// 优先级为 bottom>top>right>leftstart>center>end
if (placement === 'auto') {
const windowWidth = $window.width();
const windowHeight = $window.height();
let position;
let alignment;
if (windowHeight - triggerRect.top - triggerRect.height >
panelRect.height + screenMargin) {
// 下方放得下,放下方
position = 'bottom';
}
else if (triggerRect.top > panelRect.height + screenMargin) {
// 上方放得下,放上方
position = 'top';
}
else if (windowWidth - triggerRect.left - triggerRect.width >
panelRect.width + screenMargin) {
// 右侧放得下,放右侧
position = 'right';
}
else if (triggerRect.left > panelRect.width + screenMargin) {
// 左侧放得下,放左侧
position = 'left';
}
else {
// 默认放下方
position = 'bottom';
}
if (['top', 'bottom'].includes(position)) {
if (windowWidth - triggerRect.left > panelRect.width + screenMargin) {
// 左对齐放得下,左对齐
alignment = 'start';
}
else if (triggerRect.left + triggerRect.width / 2 >
panelRect.width / 2 + screenMargin &&
windowWidth - triggerRect.left - triggerRect.width / 2 >
panelRect.width / 2 + screenMargin) {
// 居中对齐放得下,居中对齐
alignment = undefined;
}
else if (triggerRect.left + triggerRect.width >
panelRect.width + screenMargin) {
// 右对齐放得下,右对齐
alignment = 'end';
}
else {
// 默认左对齐
alignment = 'start';
}
}
else {
if (windowHeight - triggerRect.top > panelRect.height + screenMargin) {
// 顶部对齐放得下,顶部对齐
alignment = 'start';
}
else if (triggerRect.top + triggerRect.height / 2 >
panelRect.height / 2 + screenMargin &&
windowHeight - triggerRect.top - triggerRect.height / 2 >
panelRect.height / 2 + screenMargin) {
// 居中对齐放得下,居中对齐
alignment = undefined;
}
else if (triggerRect.top + triggerRect.height >
panelRect.height + screenMargin) {
// 底部对齐放得下,底部对齐
alignment = 'end';
}
else {
// 默认顶部对齐
alignment = 'start';
}
}
placement = alignment
? [position, alignment].join('-')
: position;
}
// 根据 placement 计算 panel 的位置和方向
const [position, alignment] = placement.split('-');
this.animateDirection = ['top', 'bottom'].includes(position)
? 'vertical'
: 'horizontal';
switch (position) {
case 'top':
transformOriginY = 'bottom';
top = triggerRect.top - panelRect.height;
break;
case 'bottom':
transformOriginY = 'top';
top = triggerRect.top + triggerRect.height;
break;
default:
transformOriginY = 'center';
switch (alignment) {
case 'start':
top = triggerRect.top;
break;
case 'end':
top = triggerRect.top + triggerRect.height - panelRect.height;
break;
default:
top =
triggerRect.top + triggerRect.height / 2 - panelRect.height / 2;
break;
}
break;
}
switch (position) {
case 'left':
transformOriginX = 'right';
left = triggerRect.left - panelRect.width;
break;
case 'right':
transformOriginX = 'left';
left = triggerRect.left + triggerRect.width;
break;
default:
transformOriginX = 'center';
switch (alignment) {
case 'start':
left = triggerRect.left;
break;
case 'end':
left = triggerRect.left + triggerRect.width - panelRect.width;
break;
default:
left =
triggerRect.left + triggerRect.width / 2 - panelRect.width / 2;
break;
}
break;
}
$panel.css({
top,
left,
transformOrigin: [transformOriginX, transformOriginY].join(' '),
});
}
};
Dropdown.styles = [componentStyle, style];
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Dropdown.prototype, "open", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Dropdown.prototype, "disabled", void 0);
__decorate([
property({ reflect: true })
], Dropdown.prototype, "trigger", void 0);
__decorate([
property({ reflect: true })
], Dropdown.prototype, "placement", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
attribute: 'stay-open-on-click',
})
], Dropdown.prototype, "stayOpenOnClick", void 0);
__decorate([
property({ type: Number, reflect: true, attribute: 'open-delay' })
], Dropdown.prototype, "openDelay", void 0);
__decorate([
property({ type: Number, reflect: true, attribute: 'close-delay' })
], Dropdown.prototype, "closeDelay", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
attribute: 'open-on-pointer',
})
], Dropdown.prototype, "openOnPointer", void 0);
__decorate([
queryAssignedElements({ slot: 'trigger', flatten: true })
], Dropdown.prototype, "triggerElements", void 0);
__decorate([
queryAssignedElements({ flatten: true })
], Dropdown.prototype, "panelElements", void 0);
__decorate([
watch('placement', true),
watch('openOnPointer', true)
], Dropdown.prototype, "onPositionChange", null);
__decorate([
watch('open')
], Dropdown.prototype, "onOpenChange", null);
Dropdown = __decorate([
customElement('mdui-dropdown')
], Dropdown);
export { Dropdown };

View File

@@ -0,0 +1 @@
export declare const style: import("lit").CSSResult;

View File

@@ -0,0 +1,2 @@
import { css } from 'lit';
export const style = css `:host{--z-index:2100;display:contents}.panel{display:block;position:fixed;z-index:var(--z-index)}`;

View File

@@ -0,0 +1 @@
export * from './fab/index.js';

View File

@@ -0,0 +1 @@
export * from './fab/index.js';

View File

@@ -0,0 +1,76 @@
import { ButtonBase } from '../button/button-base.js';
import '../icon.js';
import type { Ripple } from '../ripple/index.js';
import type { TemplateResult, CSSResultGroup } from 'lit';
/**
* @summary 浮动操作按钮组件
*
* ```html
* <mdui-fab icon="edit"></mdui-fab>
* ```
*
* @event focus - 获得焦点时触发
* @event blur - 失去焦点时触发
* @event invalid - 表单字段验证未通过时触发
*
* @slot - 文本
* @slot icon - 图标
*
* @csspart button - 内部的 `<button>` 或 `<a>` 元素
* @csspart label - 右侧的文本
* @csspart icon - 左侧的图标
* @csspart loading - 加载中状态的 `<mdui-circular-progress>` 元素
*
* @cssprop --shape-corner-small - `size="small"` 时,组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
* @cssprop --shape-corner-normal - `size="normal"` 时,组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
* @cssprop --shape-corner-large - `size="large"` 时,组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
*/
export declare class Fab extends ButtonBase<FabEventMap> {
static styles: CSSResultGroup;
/**
* FAB 的形状,此组件的不同形状之间只有颜色不一样。可选值包括:
*
* * `primary`:使用 Primary container 背景色
* * `surface`:使用 Surface container high 背景色
* * `secondary`:使用 Secondary container 背景色
* * `tertiary`:使用 Tertiary container 背景色
*/
variant: /*使用 Primary container 背景色*/ 'primary' | /*使用 Surface container high 背景色*/ 'surface' | /*使用 Secondary container 背景色*/ 'secondary' | /*使用 Tertiary container 背景色*/ 'tertiary';
/**
* FAB 的大小。可选值包括:
* * `normal`:普通大小 FAB
* * `small`:小型 FAB
* * `large`:大型 FAB
*/
size: /*普通大小 FAB*/ 'normal' | /*小型 FAB*/ 'small' | /*大型 FAB*/ 'large';
/**
* Material Icons 图标名。也可以通过 `slot="icon"` 设置
*/
icon?: string;
/**
* 是否为展开状态
*/
extended: boolean;
private readonly rippleRef;
private readonly hasSlotController;
private readonly definedController;
protected get rippleElement(): Ripple;
/**
* extended 变更时,设置动画
*/
private onExtendedChange;
protected render(): TemplateResult;
private renderLabel;
private renderIcon;
private renderInner;
}
export interface FabEventMap {
focus: FocusEvent;
blur: FocusEvent;
invalid: CustomEvent<void>;
}
declare global {
interface HTMLElementTagNameMap {
'mdui-fab': Fab;
}
}

View File

@@ -0,0 +1,149 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { createRef, ref } from 'lit/directives/ref.js';
import cc from 'classcat';
import { DefinedController } from '@mdui/shared/controllers/defined.js';
import { HasSlotController } from '@mdui/shared/controllers/has-slot.js';
import { watch } from '@mdui/shared/decorators/watch.js';
import { booleanConverter } from '@mdui/shared/helpers/decorator.js';
import { delay } from '@mdui/shared/helpers/delay.js';
import { nothingTemplate } from '@mdui/shared/helpers/template.js';
import { ButtonBase } from '../button/button-base.js';
import '../icon.js';
import { style } from './style.js';
/**
* @summary 浮动操作按钮组件
*
* ```html
* <mdui-fab icon="edit"></mdui-fab>
* ```
*
* @event focus - 获得焦点时触发
* @event blur - 失去焦点时触发
* @event invalid - 表单字段验证未通过时触发
*
* @slot - 文本
* @slot icon - 图标
*
* @csspart button - 内部的 `<button>` 或 `<a>` 元素
* @csspart label - 右侧的文本
* @csspart icon - 左侧的图标
* @csspart loading - 加载中状态的 `<mdui-circular-progress>` 元素
*
* @cssprop --shape-corner-small - `size="small"` 时,组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
* @cssprop --shape-corner-normal - `size="normal"` 时,组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
* @cssprop --shape-corner-large - `size="large"` 时,组件的圆角大小。可以指定一个具体的像素值;但更推荐引用[设计令牌](/docs/2/styles/design-tokens#shape-corner)
*/
let Fab = class Fab extends ButtonBase {
constructor() {
super(...arguments);
/**
* FAB 的形状,此组件的不同形状之间只有颜色不一样。可选值包括:
*
* * `primary`:使用 Primary container 背景色
* * `surface`:使用 Surface container high 背景色
* * `secondary`:使用 Secondary container 背景色
* * `tertiary`:使用 Tertiary container 背景色
*/
this.variant = 'primary';
/**
* FAB 的大小。可选值包括:
* * `normal`:普通大小 FAB
* * `small`:小型 FAB
* * `large`:大型 FAB
*/
this.size = 'normal';
/**
* 是否为展开状态
*/
this.extended = false;
this.rippleRef = createRef();
this.hasSlotController = new HasSlotController(this, 'icon');
this.definedController = new DefinedController(this, {
relatedElements: [''],
});
}
get rippleElement() {
return this.rippleRef.value;
}
/**
* extended 变更时,设置动画
*/
async onExtendedChange() {
const hasUpdated = this.hasUpdated;
if (this.extended) {
this.style.width = `${this.scrollWidth}px`;
}
else {
this.style.width = '';
}
await this.definedController.whenDefined();
await this.updateComplete;
if (this.extended && !hasUpdated) {
this.style.width = `${this.scrollWidth}px`;
}
if (!hasUpdated) {
// 延迟设置动画,避免首次渲染时也执行动画
await delay();
this.style.transitionProperty = 'box-shadow, width, bottom, transform'; // bottom, transform 在 bottom-app-bar 中用到
}
}
render() {
const className = cc({
button: true,
'has-icon': this.icon || this.hasSlotController.test('icon'),
});
return html `<mdui-ripple ${ref(this.rippleRef)} .noRipple="${this.noRipple}"></mdui-ripple>${this.isButton()
? this.renderButton({
className,
part: 'button',
content: this.renderInner(),
})
: this.disabled || this.loading
? html `<span part="button" class="_a ${className}">${this.renderInner()}</span>`
: this.renderAnchor({
className,
part: 'button',
content: this.renderInner(),
})}`;
}
renderLabel() {
return html `<slot part="label" class="label"></slot>`;
}
renderIcon() {
if (this.loading) {
return this.renderLoading();
}
return html `<slot name="icon" part="icon" class="icon">${this.icon
? html `<mdui-icon name="${this.icon}"></mdui-icon>`
: nothingTemplate}</slot>`;
}
renderInner() {
return [this.renderIcon(), this.renderLabel()];
}
};
Fab.styles = [ButtonBase.styles, style];
__decorate([
property({ reflect: true })
], Fab.prototype, "variant", void 0);
__decorate([
property({ reflect: true })
], Fab.prototype, "size", void 0);
__decorate([
property({ reflect: true })
], Fab.prototype, "icon", void 0);
__decorate([
property({
type: Boolean,
reflect: true,
converter: booleanConverter,
})
], Fab.prototype, "extended", void 0);
__decorate([
watch('extended')
], Fab.prototype, "onExtendedChange", null);
Fab = __decorate([
customElement('mdui-fab')
], Fab);
export { Fab };

View File

@@ -0,0 +1 @@
export declare const style: import("lit").CSSResult;

View File

@@ -0,0 +1,8 @@
import { css } from 'lit';
export const style = css `:host{--shape-corner-small:var(--mdui-shape-corner-small);--shape-corner-normal:var(--mdui-shape-corner-large);--shape-corner-large:var(--mdui-shape-corner-extra-large);position:relative;display:inline-block;flex-shrink:0;overflow:hidden;text-align:center;border-radius:var(--shape-corner-normal);cursor:pointer;-webkit-tap-highlight-color:transparent;transition-property:box-shadow;transition-timing-function:var(--mdui-motion-easing-emphasized);transition-duration:var(--mdui-motion-duration-medium4);width:3.5rem;height:3.5rem;box-shadow:var(--mdui-elevation-level3);font-size:var(--mdui-typescale-label-large-size);font-weight:var(--mdui-typescale-label-large-weight);letter-spacing:var(--mdui-typescale-label-large-tracking);line-height:var(--mdui-typescale-label-large-line-height)}.button{padding:0 1rem}:host([size=small]) .button{padding:0 .5rem}:host([size=large]) .button{padding:0 1.875rem}:host([lowered]){box-shadow:var(--mdui-elevation-level1)}:host([focus-visible]){box-shadow:var(--mdui-elevation-level3)}:host([lowered][focus-visible]){box-shadow:var(--mdui-elevation-level1)}:host([pressed]){box-shadow:var(--mdui-elevation-level3)}:host([lowered][pressed]){box-shadow:var(--mdui-elevation-level1)}:host([hover]){box-shadow:var(--mdui-elevation-level4)}:host([lowered][hover]){box-shadow:var(--mdui-elevation-level2)}:host([variant=primary]){color:rgb(var(--mdui-color-on-primary-container));background-color:rgb(var(--mdui-color-primary-container));--mdui-comp-ripple-state-layer-color:var(
--mdui-color-on-primary-container
)}:host([variant=surface]){color:rgb(var(--mdui-color-primary));background-color:rgb(var(--mdui-color-surface-container-high));--mdui-comp-ripple-state-layer-color:var(--mdui-color-primary)}:host([variant=surface][lowered]){background-color:rgb(var(--mdui-color-surface-container-low))}:host([variant=secondary]){color:rgb(var(--mdui-color-on-secondary-container));background-color:rgb(var(--mdui-color-secondary-container));--mdui-comp-ripple-state-layer-color:var(
--mdui-color-on-secondary-container
)}:host([variant=tertiary]){color:rgb(var(--mdui-color-on-tertiary-container));background-color:rgb(var(--mdui-color-tertiary-container));--mdui-comp-ripple-state-layer-color:var(
--mdui-color-on-tertiary-container
)}:host([size=small]){border-radius:var(--shape-corner-small);width:2.5rem;height:2.5rem}:host([size=large]){border-radius:var(--shape-corner-large);width:6rem;height:6rem}:host([disabled]:not([disabled=false i])),:host([loading]:not([loading=false i])){cursor:default;pointer-events:none}:host([disabled]:not([disabled=false i])){color:rgba(var(--mdui-color-on-surface),38%);background-color:rgba(var(--mdui-color-on-surface),12%);box-shadow:var(--mdui-elevation-level0)}:host([extended]:not([extended=false i])){width:auto}.label{display:inline-flex;transition:opacity var(--mdui-motion-duration-short2) var(--mdui-motion-easing-linear) var(--mdui-motion-duration-short2);padding-left:.25rem;padding-right:.25rem}.has-icon .label{margin-left:.5rem}:host([size=small]) .has-icon .label{margin-left:.25rem}:host([size=large]) .has-icon .label{margin-left:1rem}:host(:not([extended])) .label,:host([extended=false i]) .label{opacity:0;transition-delay:0s;transition-duration:var(--mdui-motion-duration-short1)}:host([size=large]) .label{font-size:1.5em}.icon{display:inline-flex;font-size:1.71428571em}:host([size=large]) .icon{font-size:2.57142857em}.icon mdui-icon,::slotted([slot=icon]){font-size:inherit}mdui-circular-progress{display:inline-flex;width:1.5rem;height:1.5rem}:host([size=large]) mdui-circular-progress{width:2.25rem;height:2.25rem}:host([disabled]:not([disabled=false i])) mdui-circular-progress{stroke:rgba(var(--mdui-color-on-surface),38%)}`;

View File

@@ -0,0 +1 @@
export * from './icon/index.js';

View File

@@ -0,0 +1 @@
export * from './icon/index.js';

View File

@@ -0,0 +1,31 @@
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import type { TemplateResult, CSSResultGroup } from 'lit';
/**
* @summary 图标组件
*
* ```html
* <mdui-icon name="search"></mdui-icon>
* ```
*
* @slot - `svg` 图标的内容
*/
export declare class Icon extends MduiElement<IconEventMap> {
static styles: CSSResultGroup;
/**
* Material Icons 图标名
*/
name?: string;
/**
* svg 图标的路径
*/
src?: string;
private readonly hasSlotController;
protected render(): TemplateResult;
}
export interface IconEventMap {
}
declare global {
interface HTMLElementTagNameMap {
'mdui-icon': Icon;
}
}

View File

@@ -0,0 +1,59 @@
import { __decorate } from "tslib";
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { unsafeSVG } from 'lit/directives/unsafe-svg.js';
import { until } from 'lit/directives/until.js';
import { ajax } from '@mdui/jq/functions/ajax.js';
import { MduiElement } from '@mdui/shared/base/mdui-element.js';
import { HasSlotController } from '@mdui/shared/controllers/has-slot.js';
import { componentStyle } from '@mdui/shared/lit-styles/component-style.js';
import { style } from './style.js';
/**
* @summary 图标组件
*
* ```html
* <mdui-icon name="search"></mdui-icon>
* ```
*
* @slot - `svg` 图标的内容
*/
let Icon = class Icon extends MduiElement {
constructor() {
super(...arguments);
this.hasSlotController = new HasSlotController(this, '[default]');
}
render() {
const renderDefault = () => {
if (this.name) {
const [name, variant] = this.name.split('--');
const familyMap = new Map([
['outlined', 'Material Icons Outlined'],
['filled', 'Material Icons'],
['rounded', 'Material Icons Round'],
['sharp', 'Material Icons Sharp'],
['two-tone', 'Material Icons Two Tone'],
]);
return html `<span translate="no" style="${styleMap({ fontFamily: familyMap.get(variant) })}">${name}</span>`;
}
if (this.src) {
return html `${until(ajax({ url: this.src }).then(unsafeSVG))}`;
}
return html ``;
};
return this.hasSlotController.test('[default]')
? html `<slot></slot>`
: renderDefault();
}
};
Icon.styles = [componentStyle, style];
__decorate([
property({ reflect: true })
], Icon.prototype, "name", void 0);
__decorate([
property({ reflect: true })
], Icon.prototype, "src", void 0);
Icon = __decorate([
customElement('mdui-icon')
], Icon);
export { Icon };

View File

@@ -0,0 +1 @@
export declare const style: import("lit").CSSResult;

View File

@@ -0,0 +1,2 @@
import { css } from 'lit';
export const style = css `:host{display:inline-block;width:1em;height:1em;font-weight:400;font-family:'Material Icons';font-display:block;font-style:normal;line-height:1;direction:ltr;letter-spacing:normal;white-space:nowrap;text-transform:none;word-wrap:normal;-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;-moz-osx-font-smoothing:grayscale;font-size:1.5rem}::slotted(svg),svg{width:100%;height:100%;fill:currentcolor}`;

Some files were not shown because too many files have changed in this diff Show More