diff --git a/packages/duoyun-ui/docs/en/02-elements/banner.md b/packages/duoyun-ui/docs/en/02-elements/banner.md index 4c27d813..1e561252 100644 --- a/packages/duoyun-ui/docs/en/02-elements/banner.md +++ b/packages/duoyun-ui/docs/en/02-elements/banner.md @@ -4,7 +4,7 @@ diff --git a/packages/duoyun-ui/docs/en/02-elements/coach-mark.md b/packages/duoyun-ui/docs/en/02-elements/coach-mark.md index 53ec6d7b..3eaacde4 100644 --- a/packages/duoyun-ui/docs/en/02-elements/coach-mark.md +++ b/packages/duoyun-ui/docs/en/02-elements/coach-mark.md @@ -18,7 +18,7 @@ setTours( preview: 'https://picsum.photos/400/300', title: 'starterAnalyticsTitle', description: 'starterAnalyticsDesc', - maskCloseable: false, + maskClosable: false, }, { title: 'starterMenuTitle', diff --git a/packages/duoyun-ui/docs/en/02-elements/tag.md b/packages/duoyun-ui/docs/en/02-elements/tag.md index d88f94b2..93f021cd 100644 --- a/packages/duoyun-ui/docs/en/02-elements/tag.md +++ b/packages/duoyun-ui/docs/en/02-elements/tag.md @@ -4,7 +4,7 @@ diff --git a/packages/duoyun-ui/docs/zh/02-elements/banner.md b/packages/duoyun-ui/docs/zh/02-elements/banner.md index 4c27d813..1e561252 100644 --- a/packages/duoyun-ui/docs/zh/02-elements/banner.md +++ b/packages/duoyun-ui/docs/zh/02-elements/banner.md @@ -4,7 +4,7 @@ diff --git a/packages/duoyun-ui/docs/zh/02-elements/coach-mark.md b/packages/duoyun-ui/docs/zh/02-elements/coach-mark.md index 50928b30..3eaacde4 100644 --- a/packages/duoyun-ui/docs/zh/02-elements/coach-mark.md +++ b/packages/duoyun-ui/docs/zh/02-elements/coach-mark.md @@ -18,6 +18,7 @@ setTours( preview: 'https://picsum.photos/400/300', title: 'starterAnalyticsTitle', description: 'starterAnalyticsDesc', + maskClosable: false, }, { title: 'starterMenuTitle', diff --git a/packages/duoyun-ui/docs/zh/02-elements/tag.md b/packages/duoyun-ui/docs/zh/02-elements/tag.md index d88f94b2..93f021cd 100644 --- a/packages/duoyun-ui/docs/zh/02-elements/tag.md +++ b/packages/duoyun-ui/docs/zh/02-elements/tag.md @@ -4,7 +4,7 @@ diff --git a/packages/duoyun-ui/package.json b/packages/duoyun-ui/package.json index 5059cb71..3ed12f8f 100644 --- a/packages/duoyun-ui/package.json +++ b/packages/duoyun-ui/package.json @@ -1,6 +1,6 @@ { "name": "duoyun-ui", - "version": "0.0.67", + "version": "1.0.0", "description": "WebComponts UI", "exports": { "./elements/*": "./elements/*.js", diff --git a/packages/duoyun-ui/src/elements/banner.ts b/packages/duoyun-ui/src/elements/banner.ts index 15666b1f..474e7123 100644 --- a/packages/duoyun-ui/src/elements/banner.ts +++ b/packages/duoyun-ui/src/elements/banner.ts @@ -82,7 +82,7 @@ type Status = 'positive' | 'notice' | 'negative' | 'default'; @customElement('dy-banner') @adoptedStyle(style) export class DuoyunBannerElement extends GemElement { - @boolattribute closeable: boolean; + @boolattribute closable: boolean; @attribute status: Status; @property action?: { text: string; handle: () => void }; @@ -101,7 +101,7 @@ export class DuoyunBannerElement extends GemElement { } constructor() { - super(); + super({ delegatesFocus: true }); this.internals.role = 'banner'; } @@ -117,12 +117,13 @@ export class DuoyunBannerElement extends GemElement { ` : ''} - ${this.closeable + ${this.closable ? html` this.close(null)} @keydown=${commonHandle} diff --git a/packages/duoyun-ui/src/elements/base/visible.ts b/packages/duoyun-ui/src/elements/base/visible.ts index 43951295..7bd19a78 100644 --- a/packages/duoyun-ui/src/elements/base/visible.ts +++ b/packages/duoyun-ui/src/elements/base/visible.ts @@ -6,20 +6,20 @@ import { GemElement, GemElementOptions } from '@mantou/gem/lib/element'; * @fires hide */ export class DuoyunVisibleBaseElement<_T = Record> extends GemElement { - @emitter visible: Emitter; + @emitter show: Emitter; @emitter hide: Emitter; - @state visibility: boolean; + @state visible: boolean; constructor(options?: GemElementOptions) { super(options); new IntersectionObserver((entries) => { const { intersectionRatio } = entries.pop()!; if (intersectionRatio > 0) { - this.visibility = true; - this.visible(null); + this.visible = true; + this.show(null); } else { - this.visibility = false; + this.visible = false; this.hide(null); } }).observe(this); diff --git a/packages/duoyun-ui/src/elements/button.ts b/packages/duoyun-ui/src/elements/button.ts index 00b7c543..feb358fb 100644 --- a/packages/duoyun-ui/src/elements/button.ts +++ b/packages/duoyun-ui/src/elements/button.ts @@ -134,7 +134,6 @@ export class DuoyunButtonElement extends GemElement { @state active: boolean; @refobject dropdownRef: RefObject; - @refobject buttonRef: RefObject; @part static button: string; @part static dropdown: string; @@ -144,7 +143,7 @@ export class DuoyunButtonElement extends GemElement { } constructor() { - super(); + super({ delegatesFocus: true }); this.addEventListener('click', () => { if (this.disabled) return; if (this.route) { @@ -184,7 +183,6 @@ export class DuoyunButtonElement extends GemElement { }
{ - this.buttonRef.element!.focus(); - }; } diff --git a/packages/duoyun-ui/src/elements/calendar.ts b/packages/duoyun-ui/src/elements/calendar.ts index 723f49d3..0733aaeb 100644 --- a/packages/duoyun-ui/src/elements/calendar.ts +++ b/packages/duoyun-ui/src/elements/calendar.ts @@ -139,6 +139,10 @@ export class DuoyunCalendarElement extends GemElement { @property highlights?: number[][]; @property renderDate?: (date: Time) => TemplateResult; + constructor() { + super({ delegatesFocus: true }); + } + #isHighlight = (date: Time) => { const t = date.valueOf(); return !!this.highlights?.some(([start, stop]) => t >= start && t <= stop); diff --git a/packages/duoyun-ui/src/elements/card.ts b/packages/duoyun-ui/src/elements/card.ts index ed07789d..b554c00b 100644 --- a/packages/duoyun-ui/src/elements/card.ts +++ b/packages/duoyun-ui/src/elements/card.ts @@ -112,7 +112,7 @@ export class DuoyunCardElement extends DuoyunLoadableBaseElement { @attribute crossorigin: 'anonymous' | 'use-credentials'; constructor() { - super(); + super({ delegatesFocus: true }); this.internals.role = 'group'; } diff --git a/packages/duoyun-ui/src/elements/carousel.ts b/packages/duoyun-ui/src/elements/carousel.ts index fe5bcfcf..b9102943 100644 --- a/packages/duoyun-ui/src/elements/carousel.ts +++ b/packages/duoyun-ui/src/elements/carousel.ts @@ -176,6 +176,10 @@ export class DuoyunCarouselElement extends GemElement { return this.interval || 3000; } + constructor() { + super({ delegatesFocus: true }); + } + state: State = { currentIndex: 0, direction: 1, diff --git a/packages/duoyun-ui/src/elements/cascader-pick.ts b/packages/duoyun-ui/src/elements/cascader-pick.ts index 95783ff4..adb045d9 100644 --- a/packages/duoyun-ui/src/elements/cascader-pick.ts +++ b/packages/duoyun-ui/src/elements/cascader-pick.ts @@ -18,7 +18,7 @@ import { commonHandle } from '../lib/hotkeys'; import { focusStyle } from '../lib/styles'; import { ContextMenu } from './menu'; -import { pickerStyle } from './pick'; +import { BasePickerElement, pickerStyle } from './pick'; import type { Option, DuoyunCascaderElement } from './cascader'; import './use'; @@ -49,9 +49,9 @@ const style = createCSSSheet(css` @adoptedStyle(style) @adoptedStyle(pickerStyle) @adoptedStyle(focusStyle) -export class DuoyunCascaderPickElement extends GemElement { +export class DuoyunCascaderPickElement extends GemElement implements BasePickerElement { @attribute placeholder: string; - @property options: Option[]; + @property options?: Option[]; @boolattribute fit: boolean; @boolattribute disabled: boolean; @boolattribute multiple: boolean; @@ -78,7 +78,7 @@ export class DuoyunCascaderPickElement extends GemElement { }; #onOpen = () => { - if (this.disabled || !this.options || !this.options.length) return; + if (this.disabled || !this.options?.length) return; ContextMenu.open( html` `; }; + + showPicker = () => this.#onOpen(); } diff --git a/packages/duoyun-ui/src/elements/cascader.ts b/packages/duoyun-ui/src/elements/cascader.ts index c172553a..77b0ae22 100644 --- a/packages/duoyun-ui/src/elements/cascader.ts +++ b/packages/duoyun-ui/src/elements/cascader.ts @@ -81,7 +81,7 @@ const token = Symbol(); export class DuoyunCascaderElement extends GemElement { @part static column: string; - @property options: Option[]; + @property options?: Option[]; @boolattribute fit: boolean; @boolattribute multiple: boolean; @globalemitter change: Emitter<(string | number)[][] | (string | number)[]>; @@ -158,12 +158,13 @@ export class DuoyunCascaderElement extends GemElement { willMount = () => { this.memo( () => { + if (!this.options) return; this.#deep = getCascaderDeep(this.options, 'children'); // init state if (!this.state.selected.length) { const selected: Option[] = []; this.#value?.[0]?.forEach((val, index) => { - const item = (index ? selected[selected.length - 1].children! : this.options).find( + const item = (index ? selected[selected.length - 1].children! : this.options!).find( (e) => val === (e.value ?? e.label), )!; selected.push(item); @@ -209,13 +210,14 @@ export class DuoyunCascaderElement extends GemElement { ? 1 : 0; }; - this.options.forEach((e) => check([], e)); + this.options?.forEach((e) => check([], e)); }, () => [this.value], ); }; render = () => { + if (!this.options) return html``; const { selected } = this.state; const listStyle = styleMap({ width: this.fit ? `${100 / this.#deep}%` : undefined }); return html` diff --git a/packages/duoyun-ui/src/elements/checkbox.ts b/packages/duoyun-ui/src/elements/checkbox.ts index 62187804..2879ce7a 100644 --- a/packages/duoyun-ui/src/elements/checkbox.ts +++ b/packages/duoyun-ui/src/elements/checkbox.ts @@ -72,7 +72,7 @@ export class DuoyunCheckboxElement extends GemElement { @attribute value: string; constructor() { - super(); + super({ delegatesFocus: true }); this.addEventListener('click', this.#onClick); } @@ -112,7 +112,7 @@ export class DuoyunCheckboxGroupElement extends GemElement { @property options?: Option[]; constructor() { - super(); + super({ delegatesFocus: true }); this.internals.role = 'group'; } diff --git a/packages/duoyun-ui/src/elements/coach-mark.ts b/packages/duoyun-ui/src/elements/coach-mark.ts index 523ab4b7..d46fc017 100644 --- a/packages/duoyun-ui/src/elements/coach-mark.ts +++ b/packages/duoyun-ui/src/elements/coach-mark.ts @@ -24,7 +24,7 @@ export type Tour = { finishText?: string; finish?: () => Promise | void; skip?: () => Promise | void; - maskCloseable?: boolean; + maskClosable?: boolean; }; let tourList: Tour[] = []; @@ -161,10 +161,10 @@ export class DuoyunCoachMarkElement extends DuoyunVisibleBaseElement { #open = async () => { if (!this.#tour) return; - const { description, preview = '', title, finishText, maskCloseable } = this.#tour; + const { description, preview = '', title, finishText, maskClosable } = this.#tour; const isFinish = store.currentIndex === tourList.length - 1; this.scrollIntoView({ block: 'nearest', inline: 'nearest' }); - if (!this.visibility) await new Promise((res) => this.addEventListener('visible', res, { once: true })); + if (!this.visible) await new Promise((res) => this.addEventListener('show', res, { once: true })); DuoyunWaitElement.instance?.remove(); // avoid inert conflict ContextMenu.open( html` @@ -188,7 +188,7 @@ export class DuoyunCoachMarkElement extends DuoyunVisibleBaseElement { `, { - maskCloseable, + maskClosable, activeElement: this, width: this.#width, }, diff --git a/packages/duoyun-ui/src/elements/color-pick.ts b/packages/duoyun-ui/src/elements/color-pick.ts index 0e31feb0..c956ef6f 100644 --- a/packages/duoyun-ui/src/elements/color-pick.ts +++ b/packages/duoyun-ui/src/elements/color-pick.ts @@ -6,6 +6,8 @@ import { globalemitter, Emitter, boolattribute, + refobject, + RefObject, } from '@mantou/gem/lib/decorators'; import { GemElement, html } from '@mantou/gem/lib/element'; import { createCSSSheet, css, styleMap } from '@mantou/gem/lib/utils'; @@ -15,6 +17,9 @@ import { theme } from '../lib/theme'; import { commonHandle } from '../lib/hotkeys'; import { focusStyle } from '../lib/styles'; +import type { BasePickerElement } from './pick'; +import type { DuoyunPopoverElement } from './popover'; + import './popover'; import './color-panel'; @@ -48,16 +53,22 @@ const style = createCSSSheet(css` @customElement('dy-color-pick') @adoptedStyle(style) @adoptedStyle(focusStyle) -export class DuoyunColorPickElement extends GemElement { +export class DuoyunColorPickElement extends GemElement implements BasePickerElement { @attribute value: HexColor; @boolattribute alpha: boolean; + @refobject popoverRef: RefObject; @globalemitter change: Emitter; + constructor() { + super({ delegatesFocus: true }); + } + render = () => { return html` `; }; + + showPicker = () => { + this.popoverRef.element?.click(); + }; } diff --git a/packages/duoyun-ui/src/elements/compartment.ts b/packages/duoyun-ui/src/elements/compartment.ts index f9246ceb..24b3bd24 100644 --- a/packages/duoyun-ui/src/elements/compartment.ts +++ b/packages/duoyun-ui/src/elements/compartment.ts @@ -14,7 +14,7 @@ const style = createCSSSheet(css` @customElement('dy-compartment') @adoptedStyle(style) export class DuoyunCompartmentElement extends GemElement { - @property content: string | number | TemplateResult; + @property content?: string | number | TemplateResult; render = () => { return html`${this.content}`; }; diff --git a/packages/duoyun-ui/src/elements/copy.ts b/packages/duoyun-ui/src/elements/copy.ts index b8b964cc..98528cf0 100644 --- a/packages/duoyun-ui/src/elements/copy.ts +++ b/packages/duoyun-ui/src/elements/copy.ts @@ -96,10 +96,6 @@ export class DuoyunCopyElement extends GemElement { @emitter copy: Emitter; - state: State = { - status: 'none', - }; - get #icon() { switch (this.state.status) { case 'success': @@ -111,6 +107,14 @@ export class DuoyunCopyElement extends GemElement { } } + constructor() { + super({ delegatesFocus: true }); + } + + state: State = { + status: 'none', + }; + #showMessage = (isSuccess: boolean) => { this.copy(isSuccess); if (!this.silent) { diff --git a/packages/duoyun-ui/src/elements/date-panel.ts b/packages/duoyun-ui/src/elements/date-panel.ts index e86aceaf..c43d3bdb 100644 --- a/packages/duoyun-ui/src/elements/date-panel.ts +++ b/packages/duoyun-ui/src/elements/date-panel.ts @@ -155,7 +155,7 @@ export class DuoyunDatePanelElement extends GemElement { } constructor() { - super(); + super({ delegatesFocus: true }); this.internals.role = 'widget'; } @@ -209,7 +209,7 @@ export class DuoyunDatePanelElement extends GemElement { this.change(evt.detail); }; - #renderCurrentPostion = () => { + #renderCurrentPosition = () => { switch (this.state.mode) { case 'day': return html`${this.#currentPosition.formatToParts().map(({ type, value }) => { @@ -315,7 +315,7 @@ export class DuoyunDatePanelElement extends GemElement { @click=${() => this.#increaseView(-1)} .element=${icons.left} > -
${this.#renderCurrentPostion()}
+
${this.#renderCurrentPosition()}
{ class="timepanel" .value=${this.value} @change=${this.#onTimeChange} - headerless + headless >
` diff --git a/packages/duoyun-ui/src/elements/date-pick.ts b/packages/duoyun-ui/src/elements/date-pick.ts index 9c1118a2..774c7b75 100644 --- a/packages/duoyun-ui/src/elements/date-pick.ts +++ b/packages/duoyun-ui/src/elements/date-pick.ts @@ -24,7 +24,7 @@ import { focusStyle } from '../lib/styles'; import type { DuoyunButtonElement } from './button'; import type { DuoyunDatePanelElement } from './date-panel'; import { ContextMenu } from './menu'; -import { pickerStyle } from './pick'; +import { BasePickerElement, pickerStyle } from './pick'; import './use'; import './date-panel'; @@ -74,7 +74,7 @@ export interface Option { @adoptedStyle(style) @adoptedStyle(focusStyle) @connectStore(icons) -export class DuoyunDatePickElement extends GemElement { +export class DuoyunDatePickElement extends GemElement implements BasePickerElement { @attribute placeholder: string; @boolattribute time: boolean; @boolattribute clearable: boolean; @@ -173,4 +173,6 @@ export class DuoyunDatePickElement extends GemElement { `; }; + + showPicker = () => this.#onOpen(); } diff --git a/packages/duoyun-ui/src/elements/date-range-pick.ts b/packages/duoyun-ui/src/elements/date-range-pick.ts index 2796a816..0b0eded5 100644 --- a/packages/duoyun-ui/src/elements/date-range-pick.ts +++ b/packages/duoyun-ui/src/elements/date-range-pick.ts @@ -22,7 +22,7 @@ import { focusStyle } from '../lib/styles'; import type { DuoyunButtonElement } from './button'; import { ContextMenu } from './menu'; -import { pickerStyle } from './pick'; +import { BasePickerElement, pickerStyle } from './pick'; import './use'; import './date-range-panel'; @@ -65,7 +65,7 @@ const style = createCSSSheet(css` @adoptedStyle(style) @adoptedStyle(pickerStyle) @adoptedStyle(focusStyle) -export class DuoyunDateRangePickElement extends GemElement { +export class DuoyunDateRangePickElement extends GemElement implements BasePickerElement { @attribute placeholder: string; @boolattribute clearable: boolean; @boolattribute disabled: boolean; @@ -176,4 +176,6 @@ export class DuoyunDateRangePickElement extends GemElement { `; }; + + showPicker = () => this.#onOpen(); } diff --git a/packages/duoyun-ui/src/elements/drop-area.ts b/packages/duoyun-ui/src/elements/drop-area.ts index bc6e1c0c..96ee1405 100644 --- a/packages/duoyun-ui/src/elements/drop-area.ts +++ b/packages/duoyun-ui/src/elements/drop-area.ts @@ -60,7 +60,7 @@ export class DuoyunDropAreaElement extends GemElement { @state allowDrop: boolean; @globalemitter change: Emitter; - @emitter omit: Emitter; + @emitter ignore: Emitter; @emitter nodata: Emitter; get #tip() { @@ -124,7 +124,7 @@ export class DuoyunDropAreaElement extends GemElement { if (validFiles.length) this.change(validFiles); const set = new Set(validFiles); const omitFiles = [...files].filter((file) => !set.has(file)); - if (omitFiles.length) this.omit(omitFiles); + if (omitFiles.length) this.ignore(omitFiles); return; } else { const type = this.#findValidType(evt.dataTransfer.types); diff --git a/packages/duoyun-ui/src/elements/file-pick.ts b/packages/duoyun-ui/src/elements/file-pick.ts index 5483b4bd..c2300176 100644 --- a/packages/duoyun-ui/src/elements/file-pick.ts +++ b/packages/duoyun-ui/src/elements/file-pick.ts @@ -20,6 +20,7 @@ import { focusStyle } from '../lib/styles'; import { utf8ToB64 } from '../lib/encode'; import type { ImageStatus } from './image-preview'; +import type { BasePickerElement } from './pick'; import './use'; import './image-preview'; @@ -118,7 +119,7 @@ export interface FileItem extends File { @customElement('dy-file-pick') @adoptedStyle(style) @adoptedStyle(focusStyle) -export class DuoyunFilePickElement extends GemElement { +export class DuoyunFilePickElement extends GemElement implements BasePickerElement { @part static button: string; @part static item: string; @@ -144,6 +145,10 @@ export class DuoyunFilePickElement extends GemElement { return this.placeholder || 'Browse'; } + constructor() { + super({ delegatesFocus: true }); + } + #onChange = () => { const input = this.inputRef.element!; if (!input.files) return; @@ -227,7 +232,7 @@ export class DuoyunFilePickElement extends GemElement { part=${DuoyunFilePickElement.button} class="item button" @keydown=${commonHandle} - @click=${() => this.openFilePicker()}> + @click=${() => this.showPicker()}> ${this.#placeholder} @@ -235,7 +240,7 @@ export class DuoyunFilePickElement extends GemElement { `; }; - openFilePicker = () => { + showPicker = () => { this.inputRef.element!.click(); }; } diff --git a/packages/duoyun-ui/src/elements/form.ts b/packages/duoyun-ui/src/elements/form.ts index 67266502..fb0c8a54 100644 --- a/packages/duoyun-ui/src/elements/form.ts +++ b/packages/duoyun-ui/src/elements/form.ts @@ -251,7 +251,7 @@ export class DuoyunFormItemElement extends GemElement { @state invalid: boolean; @property value?: number | string | any[] | any; - @property renderLabel: (e: SelectOption) => string | TemplateResult; + @property renderLabel?: (e: SelectOption) => string | TemplateResult; @property rules?: FormItemRule[]; diff --git a/packages/duoyun-ui/src/elements/image-preview.ts b/packages/duoyun-ui/src/elements/image-preview.ts index de834e75..111e2cda 100644 --- a/packages/duoyun-ui/src/elements/image-preview.ts +++ b/packages/duoyun-ui/src/elements/image-preview.ts @@ -105,6 +105,10 @@ export class DuoyunImagePreviewElement extends GemElement { } } + constructor() { + super({ delegatesFocus: true }); + } + state: State = { previewUrl: '', }; @@ -127,7 +131,7 @@ export class DuoyunImagePreviewElement extends GemElement { }; render = () => { - if (!this.file) return; + if (!this.file) return html``; const { previewUrl } = this.state; const color = this.progress ? theme.informativeColor : this.#color; return html` diff --git a/packages/duoyun-ui/src/elements/input.ts b/packages/duoyun-ui/src/elements/input.ts index 2190179b..b9665fa8 100644 --- a/packages/duoyun-ui/src/elements/input.ts +++ b/packages/duoyun-ui/src/elements/input.ts @@ -210,6 +210,10 @@ export class DuoyunInputElement extends GemElement { return this.hasAttribute('max') ? this.max : Infinity; } + constructor() { + super({ delegatesFocus: true }); + } + #nextState = { value: '', selectionStart: 0, @@ -395,16 +399,6 @@ export class DuoyunInputElement extends GemElement { : ''} `; } - - focus = () => { - this.inputRef.element?.focus(); - }; - - blur = () => { - if (this.shadowRoot!.activeElement instanceof HTMLElement) { - this.shadowRoot!.activeElement.blur(); - } - }; } const inputGroupStyle = createCSSSheet(css` diff --git a/packages/duoyun-ui/src/elements/menu.ts b/packages/duoyun-ui/src/elements/menu.ts index a471f9c0..b65d6620 100644 --- a/packages/duoyun-ui/src/elements/menu.ts +++ b/packages/duoyun-ui/src/elements/menu.ts @@ -34,7 +34,7 @@ type MenuStore = { maxHeight?: string; activeElement?: HTMLElement | null; openLeft?: boolean; - maskCloseable?: boolean; + maskClosable?: boolean; menuStack: { searchable?: boolean; openTop?: boolean; @@ -50,7 +50,7 @@ type OpenMenuOptions = { activeElement?: HTMLElement | null; /**only work `activeElement`, only support first menu */ openLeft?: boolean; - maskCloseable?: boolean; + maskClosable?: boolean; /**only work nothing `activeElement` */ x?: number; /**only work nothing `activeElement` */ @@ -124,7 +124,7 @@ export class DuoyunMenuElement extends GemElement { maxHeight, searchable, header, - maskCloseable = true, + maskClosable = true, } = options; if (Array.isArray(menu) && menu.length === 0) throw new Error('menu length is 0'); toggleActiveState(activeElement, true); @@ -133,7 +133,7 @@ export class DuoyunMenuElement extends GemElement { maxHeight, activeElement, openLeft, - maskCloseable, + maskClosable, menuStack: [{ x, y, menu, searchable, header }], }); if (ContextMenu.instance) { @@ -192,7 +192,7 @@ export class DuoyunMenuElement extends GemElement { } constructor() { - super(); + super({ delegatesFocus: true }); document.body.append(this); } @@ -282,7 +282,7 @@ export class DuoyunMenuElement extends GemElement { }; #onMaskClick = () => { - if (menuStore.maskCloseable) { + if (menuStore.maskClosable) { ContextMenu.close(); } }; @@ -300,9 +300,9 @@ export class DuoyunMenuElement extends GemElement { }; render = () => { - const { menuStack, maxHeight, maskCloseable } = menuStore; + const { menuStack, maxHeight, maskClosable } = menuStore; return html` -
+
${menuStack.map( ( { x, y, menu, searchable, openTop, header }, diff --git a/packages/duoyun-ui/src/elements/modal.ts b/packages/duoyun-ui/src/elements/modal.ts index 4ffdc976..10295f58 100644 --- a/packages/duoyun-ui/src/elements/modal.ts +++ b/packages/duoyun-ui/src/elements/modal.ts @@ -138,7 +138,7 @@ export interface Options { body?: string | TemplateResult; /**render body only */ customize?: boolean; - maskCloseable?: boolean; + maskClosable?: boolean; open?: boolean; disableDefaultCancelBtn?: boolean; disableDefaultOKBtn?: boolean; @@ -160,7 +160,7 @@ export interface Options { export class DuoyunModalElement extends GemElement { @boolattribute open: boolean; @boolattribute customize: boolean; - @boolattribute maskCloseable: boolean; + @boolattribute maskClosable: boolean; @attribute okText: string; @attribute cancelText: string; @boolattribute disableDefaultCancelBtn: boolean; @@ -171,7 +171,7 @@ export class DuoyunModalElement extends GemElement { @emitter ok: Emitter; @emitter maskclick: Emitter; - @property header: string | TemplateResult; + @property header?: string | TemplateResult; @property body?: string | TemplateResult; @refobject bodyRef: RefObject; @@ -217,7 +217,7 @@ export class DuoyunModalElement extends GemElement { header, open, customize, - maskCloseable, + maskClosable, cancelText, okText, body, @@ -225,10 +225,10 @@ export class DuoyunModalElement extends GemElement { disableDefaultOKBtn, dangerDefaultOkBtn, }: Options = {}) { - super(); + super({ delegatesFocus: true }); if (header) this.header = header; if (customize) this.customize = customize; - if (maskCloseable) this.maskCloseable = maskCloseable; + if (maskClosable) this.maskClosable = maskClosable; if (open) this.open = open; if (cancelText) this.cancelText = cancelText; if (okText) this.okText = okText; @@ -246,14 +246,10 @@ export class DuoyunModalElement extends GemElement { this.ok(null); }; - #focus = () => { - this.shadowRoot!.querySelector('.dialog')?.focus(); - }; - #onMaskClick = () => { - if (this.maskCloseable) this.#close(); + if (this.maskClosable) this.#close(); this.maskclick(null); - this.#focus(); + this.focus(); }; #keydown = (evt: KeyboardEvent) => { @@ -265,7 +261,7 @@ export class DuoyunModalElement extends GemElement { this.effect( () => { if (this.open && !this.shadowRoot?.activeElement) { - this.#focus(); + this.focus(); } }, () => [this.open], diff --git a/packages/duoyun-ui/src/elements/pagination.ts b/packages/duoyun-ui/src/elements/pagination.ts index 3681a51b..a09c6ff8 100644 --- a/packages/duoyun-ui/src/elements/pagination.ts +++ b/packages/duoyun-ui/src/elements/pagination.ts @@ -95,7 +95,7 @@ export class DuoyunPaginationElement extends GemElement { } constructor() { - super(); + super({ delegatesFocus: true }); this.internals.role = 'listbox'; this.internals.ariaLabel = 'Pagination'; } @@ -113,7 +113,7 @@ export class DuoyunPaginationElement extends GemElement { close(); }; const openMore = (evt: Event) => { - close = Popover.show(evt.target as Element, { + close = Popover.open(evt.target as Element, { trigger: 'click', content: html` diff --git a/packages/duoyun-ui/src/elements/pick.ts b/packages/duoyun-ui/src/elements/pick.ts index dca52949..a6be58af 100644 --- a/packages/duoyun-ui/src/elements/pick.ts +++ b/packages/duoyun-ui/src/elements/pick.ts @@ -57,6 +57,10 @@ export const pickerStyle = createCSSSheet(css` } `); +export abstract class BasePickerElement { + showPicker: () => void; +} + const style = createCSSSheet(css` :host { width: 12em; @@ -92,7 +96,7 @@ export interface Option { @adoptedStyle(pickerStyle) @adoptedStyle(focusStyle) @connectStore(icons) -export class DuoyunPickElement extends GemElement { +export class DuoyunPickElement extends GemElement implements BasePickerElement { @attribute placeholder: string; @boolattribute disabled: boolean; @boolattribute borderless: boolean; @@ -170,4 +174,6 @@ export class DuoyunPickElement extends GemElement { `; }; + + showPicker = () => this.#onOpen(); } diff --git a/packages/duoyun-ui/src/elements/popover.ts b/packages/duoyun-ui/src/elements/popover.ts index 775a2251..9d3f77a9 100644 --- a/packages/duoyun-ui/src/elements/popover.ts +++ b/packages/duoyun-ui/src/elements/popover.ts @@ -80,10 +80,10 @@ export class DuoyunPopoverElement extends GemElement { @emitter open: Emitter; @emitter close: Emitter; - static show(e: Element, option: Option): CloseCallback; - static show(x: number, y: number, option: Option): CloseCallback; - static show(xywh: number[], option: Option): CloseCallback; - static show(...args: any[]) { + static open(e: Element, option: Option): CloseCallback; + static open(x: number, y: number, option: Option): CloseCallback; + static open(xywh: number[], option: Option): CloseCallback; + static open(...args: any[]) { const firstArg = args[0]; const option = args.pop(); const element = firstArg instanceof Element ? firstArg : null; @@ -146,7 +146,7 @@ export class DuoyunPopoverElement extends GemElement { }; constructor({ delay = 500, content, position, ghostStyle, trigger, unreachable }: Option = {}) { - super(); + super({ delegatesFocus: true }); if (content) this.content = content; if (position) this.position = position; if (ghostStyle) this.ghostStyle = ghostStyle; diff --git a/packages/duoyun-ui/src/elements/radio.ts b/packages/duoyun-ui/src/elements/radio.ts index 49ef7551..4b2f84dd 100644 --- a/packages/duoyun-ui/src/elements/radio.ts +++ b/packages/duoyun-ui/src/elements/radio.ts @@ -61,7 +61,7 @@ export class DuoyunRadioElement extends GemElement { @attribute value: string; constructor() { - super(); + super({ delegatesFocus: true }); this.addEventListener('click', this.#onClick); } @@ -122,7 +122,7 @@ export class DuoyunRadioGroupElement extends GemElement { @property options?: Option[]; constructor() { - super(); + super({ delegatesFocus: true }); this.internals.role = 'radiogroup'; } diff --git a/packages/duoyun-ui/src/elements/rating.ts b/packages/duoyun-ui/src/elements/rating.ts index 4cbaa972..a9aff9ec 100644 --- a/packages/duoyun-ui/src/elements/rating.ts +++ b/packages/duoyun-ui/src/elements/rating.ts @@ -77,7 +77,7 @@ export class DuoyunRatingElement extends GemElement { } constructor() { - super(); + super({ delegatesFocus: true }); this.internals.role = 'range'; this.effect(() => { this.internals.ariaDisabled = String(this.disabled); diff --git a/packages/duoyun-ui/src/elements/select.ts b/packages/duoyun-ui/src/elements/select.ts index e53093cd..014c54e2 100644 --- a/packages/duoyun-ui/src/elements/select.ts +++ b/packages/duoyun-ui/src/elements/select.ts @@ -23,7 +23,7 @@ import { hotkeys } from '../lib/hotkeys'; import { isNotNullish } from '../lib/types'; import { focusStyle } from '../lib/styles'; -import { pickerStyle } from './pick'; +import { BasePickerElement, pickerStyle } from './pick'; import type { Adder } from './options'; import './reflect'; @@ -126,7 +126,7 @@ type State = { @adoptedStyle(pickerStyle) @adoptedStyle(focusStyle) @connectStore(icons) -export class DuoyunSelectElement extends GemElement { +export class DuoyunSelectElement extends GemElement implements BasePickerElement { @boolattribute multiple: boolean; @boolattribute disabled: boolean; @boolattribute searchable: boolean; @@ -140,8 +140,8 @@ export class DuoyunSelectElement extends GemElement { @property options?: Option[]; @property adder?: Adder; @property value?: any | any[]; - @property renderLabel: (e: Option) => string | TemplateResult; - @property renderTag: (e: Option) => string | TemplateResult; + @property renderLabel?: (e: Option) => string | TemplateResult; + @property renderTag?: (e: Option) => string | TemplateResult; @state active: boolean; @globalemitter change: Emitter; @@ -178,7 +178,7 @@ export class DuoyunSelectElement extends GemElement { this.internals.ariaExpanded = String(this.state.open); this.internals.ariaReadOnly = String(this.disabled); }); - this.addEventListener('click', this.open); + this.addEventListener('click', this.#open); this.addEventListener('keydown', this.#onKeydown); } @@ -192,33 +192,33 @@ export class DuoyunSelectElement extends GemElement { search: '', }; - open = () => { + #open = () => { if (this.disabled) return; this.setState({ open: true }); }; - close = () => { + #close = () => { this.setState({ open: false }); }; #onKeydown = hotkeys({ esc: (evt) => { if (this.state.open) { - this.close(); + this.#close(); if (!this.inline) { evt.stopPropagation(); } } }, 'space,enter': (evt) => { - this.open(); + this.#open(); this.searchRef.element?.focus(); evt.preventDefault(); }, }); #onEsc = (evt: KeyboardEvent) => { - this.close(); + this.#close(); (this.searchRef.element || this).focus(); evt.stopPropagation(); }; @@ -324,11 +324,11 @@ export class DuoyunSelectElement extends GemElement { maxHeight: isShowTop ? `${top - 8}px` : `calc(100vh - ${bottom + 8}px)`, transform: isShowTop ? `translateY(calc(-100% - 8px - ${height}px))` : 'none', }); - addEventListener('pointerup', this.close); + addEventListener('pointerup', this.#close); } else { this.setState({ open: false }); } - return () => removeEventListener('pointerup', this.close); + return () => removeEventListener('pointerup', this.#close); }, () => [this.state.open], ); @@ -389,7 +389,7 @@ export class DuoyunSelectElement extends GemElement { evt.stopPropagation()} @close=${() => this.#onRemoveTag(this.#valueOptions![index])} > @@ -446,4 +446,6 @@ export class DuoyunSelectElement extends GemElement { `} `; }; + + showPicker = () => this.#open(); } diff --git a/packages/duoyun-ui/src/elements/selection-box.ts b/packages/duoyun-ui/src/elements/selection-box.ts index be345f6b..5fe7ec88 100644 --- a/packages/duoyun-ui/src/elements/selection-box.ts +++ b/packages/duoyun-ui/src/elements/selection-box.ts @@ -42,7 +42,7 @@ export type SelectionChange = { @adoptedStyle(style) export class DuoyunSelectionBoxElement extends GemElement { @emitter change: Emitter; - @state select: boolean; + @state selecting: boolean; state: State = { rect: { @@ -65,7 +65,7 @@ export class DuoyunSelectionBoxElement extends GemElement { #onPointerDown = (evt: PointerEvent) => { if (evt.altKey) return; document.getSelection()?.removeAllRanges(); - this.select = true; + this.selecting = true; this.setState({ start: [evt.x, evt.y], mode: this.#getMode(evt) }); addEventListener('pointermove', this.#onPointerMove); addEventListener('pointerup', this.#onPointerUp); @@ -74,7 +74,7 @@ export class DuoyunSelectionBoxElement extends GemElement { }; #onPointerUp = () => { - this.select = false; + this.selecting = false; this.setState({ start: undefined, stop: undefined }); removeEventListener('pointermove', this.#onPointerMove); removeEventListener('pointerup', this.#onPointerUp); diff --git a/packages/duoyun-ui/src/elements/side-navigation.ts b/packages/duoyun-ui/src/elements/side-navigation.ts index 78ab9826..ca911333 100644 --- a/packages/duoyun-ui/src/elements/side-navigation.ts +++ b/packages/duoyun-ui/src/elements/side-navigation.ts @@ -87,13 +87,13 @@ type State = Record; @adoptedStyle(focusStyle) @connectStore(history.store) export class DuoyunSideNavigationElement extends DuoyunScrollBaseElement { - @property items: NavItems = []; + @property items?: NavItems = []; // children open state state: State = {}; constructor() { - super(); + super({ delegatesFocus: true }); this.internals.role = 'navigation'; this.internals.ariaLabel = 'Side Navigation'; } @@ -117,7 +117,7 @@ export class DuoyunSideNavigationElement extends DuoyunScrollBaseElement return true; } }; - this.items.forEach(matchChildren); + this.items?.forEach(matchChildren); }; #renderItem = ({ pattern, title = '', slot, params, query, hash, children }: Item): TemplateResult => { @@ -147,7 +147,7 @@ export class DuoyunSideNavigationElement extends DuoyunScrollBaseElement }; render = () => { - if (!this.items.length) return html``; + if (!this.items?.length) return html``; return html`${this.items.map((item) => 'group' in item ? html` diff --git a/packages/duoyun-ui/src/elements/switch.ts b/packages/duoyun-ui/src/elements/switch.ts index 1f369c55..3129e705 100644 --- a/packages/duoyun-ui/src/elements/switch.ts +++ b/packages/duoyun-ui/src/elements/switch.ts @@ -79,7 +79,7 @@ export class DuoyunSwitchElement extends GemElement { } constructor() { - super(); + super({ delegatesFocus: true }); this.addEventListener('click', this.#onClick); } diff --git a/packages/duoyun-ui/src/elements/table.ts b/packages/duoyun-ui/src/elements/table.ts index 0c079612..4c1bb43c 100644 --- a/packages/duoyun-ui/src/elements/table.ts +++ b/packages/duoyun-ui/src/elements/table.ts @@ -45,7 +45,7 @@ const style = createCSSSheet(css` table-layout: fixed; border-collapse: collapse; } - .selection:where(:--select, :state(select)) ~ table { + .selection:where(:--selecting, :state(selecting)) ~ table { user-select: none; } .selection ~ table { @@ -123,8 +123,8 @@ export type Column = { dataIndex?: keyof T | string[]; render?: (record: T) => string | TemplateResult; getActions?: (record: T, evt: HTMLElement) => MenuItem[]; - getColspan?: (record: T, arr: T[]) => number; - getRowspan?: (record: T, arr: T[]) => number; + getColSpan?: (record: T, arr: T[]) => number; + getRowSpan?: (record: T, arr: T[]) => number; data?: Record; }; @@ -156,8 +156,8 @@ export class DuoyunTableElement extends GemElement { @emitter itemclick: Emitter; @emitter itemcontextmenu: Emitter; - @property columns: Column[]; - @property data: (Record | undefined)[] | undefined; + @property columns?: Column[]; + @property data?: (Record | undefined)[]; @property getRowStyle?: (record: any) => StyleObject; @property noData?: string | TemplateResult; @@ -168,6 +168,10 @@ export class DuoyunTableElement extends GemElement { #selectionSet = new Set(); + constructor() { + super({ delegatesFocus: true }); + } + #onSelectionBoxChange = ({ detail: { rect, mode } }: CustomEvent) => { const selection = new Set(mode === 'new' ? [] : this.#selectionSet); const boxWidth = rect.width; @@ -286,6 +290,7 @@ export class DuoyunTableElement extends GemElement { }; render = () => { + if (!this.columns) return html``; const columns = this.expandedRowRender ? [this.#expandedColumn, ...this.columns] : this.columns; const rowSpanMemo = columns.map(() => 0); return html` @@ -348,18 +353,18 @@ export class DuoyunTableElement extends GemElement { getActions, width, style = this.#getDefaultStyle(width), - getRowspan, - getColspan, + getRowSpan, + getColSpan, ellipsis = false, }, colIndex, _arr, rowShouldRender = this.#shouldRenderTd(rowSpanMemo, colIndex), rowSpan = rowShouldRender - ? this.#getSpan(rowSpanMemo, colIndex, getRowspan?.(record, this.data!)) + ? this.#getSpan(rowSpanMemo, colIndex, getRowSpan?.(record, this.data!)) : 1, colShouldRender = this.#shouldRenderTd(colSpanMemo, 0), - colSpan = colShouldRender ? this.#getSpan(colSpanMemo, 0, getColspan?.(record, this.data!)) : 1, + colSpan = colShouldRender ? this.#getSpan(colSpanMemo, 0, getColSpan?.(record, this.data!)) : 1, ) => rowShouldRender && colShouldRender ? html` diff --git a/packages/duoyun-ui/src/elements/tabs.ts b/packages/duoyun-ui/src/elements/tabs.ts index f4740736..939a78a6 100644 --- a/packages/duoyun-ui/src/elements/tabs.ts +++ b/packages/duoyun-ui/src/elements/tabs.ts @@ -111,7 +111,7 @@ export class DuoyunTabsElement extends GemElement { } constructor() { - super(); + super({ delegatesFocus: true }); this.internals.role = 'tablist'; } diff --git a/packages/duoyun-ui/src/elements/tag.ts b/packages/duoyun-ui/src/elements/tag.ts index f14ad8f9..3c64bf45 100644 --- a/packages/duoyun-ui/src/elements/tag.ts +++ b/packages/duoyun-ui/src/elements/tag.ts @@ -56,7 +56,7 @@ export type PresetColor = 'positive' | 'informative' | 'negative' | 'notice' | ' @adoptedStyle(focusStyle) @connectStore(icons) export class DuoyunTagElement extends GemElement { - @boolattribute closeable: boolean; + @boolattribute closable: boolean; @attribute color: StringList; @attribute mode: 'solid' | 'reverse'; @boolattribute small: boolean; @@ -80,7 +80,7 @@ export class DuoyunTagElement extends GemElement { } constructor() { - super(); + super({ delegatesFocus: true }); this.internals.role = 'mark'; } @@ -102,7 +102,7 @@ export class DuoyunTagElement extends GemElement { } - ${this.closeable + ${this.closable ? html` ; @property value?: number; @@ -65,6 +67,10 @@ export class DuoyunTimePanelElement extends GemElement { return isNotNullish(this.value) ? parseDate(this.value) : ({} as Partial>); } + constructor() { + super({ delegatesFocus: true }); + } + #toArr = (l: number) => Array.from({ length: l }, (_, index) => index); #toString = (n: number) => n.toString().padStart(2, '0'); @@ -89,7 +95,7 @@ export class DuoyunTimePanelElement extends GemElement { render = () => { const { hour, minute, second } = this.#parts; return html` - ${this.headerless + ${this.headless ? '' : html`
${locale.hour}
diff --git a/packages/duoyun-ui/src/elements/time-pick.ts b/packages/duoyun-ui/src/elements/time-pick.ts index 42538186..ae64e667 100644 --- a/packages/duoyun-ui/src/elements/time-pick.ts +++ b/packages/duoyun-ui/src/elements/time-pick.ts @@ -21,7 +21,7 @@ import { commonHandle } from '../lib/hotkeys'; import { focusStyle } from '../lib/styles'; import { ContextMenu } from './menu'; -import { pickerStyle } from './pick'; +import { BasePickerElement, pickerStyle } from './pick'; import type { DuoyunButtonElement } from './button'; import type { DuoyunTimePanelElement } from './time-panel'; @@ -59,7 +59,7 @@ const style = createCSSSheet(css` @adoptedStyle(style) @adoptedStyle(pickerStyle) @adoptedStyle(focusStyle) -export class DuoyunTimePickElement extends GemElement { +export class DuoyunTimePickElement extends GemElement implements BasePickerElement { @attribute placeholder: string; @boolattribute clearable: boolean; @boolattribute disabled: boolean; @@ -154,4 +154,6 @@ export class DuoyunTimePickElement extends GemElement { `; }; + + showPicker = () => this.#onOpen(); } diff --git a/packages/duoyun-ui/src/elements/tree.ts b/packages/duoyun-ui/src/elements/tree.ts index 292e05da..febb09a8 100644 --- a/packages/duoyun-ui/src/elements/tree.ts +++ b/packages/duoyun-ui/src/elements/tree.ts @@ -91,14 +91,15 @@ class _DuoyunTreeItemElement extends GemElement { @boolattribute highlight: boolean; @boolattribute hastags: boolean; - @property item: TreeItem; + @property item?: TreeItem; constructor() { - super(); + super({ delegatesFocus: true }); this.internals.role = 'treeitem'; } render = () => { + if (!this.item) return html``; const { label, children, icon, context, tags } = this.item; return html` { }; constructor() { - super(); + super({ delegatesFocus: true }); this.internals.role = 'tree'; } diff --git a/packages/gem/docs/en/003-api/001-gem-element.md b/packages/gem/docs/en/003-api/001-gem-element.md index 5a17877f..c8828e01 100644 --- a/packages/gem/docs/en/003-api/001-gem-element.md +++ b/packages/gem/docs/en/003-api/001-gem-element.md @@ -13,12 +13,12 @@ class GemElement extends HTMLElement { ## Construction parameters -| name | description | -| --------- | ------------------------------------------ | -| `isLight` | Whether to render as Light DOM | -| `isAsync` | Whether to use non-blocking rendering mode | - -_The [`delegatesFocus`](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow) parameter is not supported temporarily, you can use [`:focus-within`](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within)_ +| name | description | +| ---------------- | ------------------------------------------------------------------------- | +| `isLight` | Whether to render as Light DOM | +| `isAsync` | Whether to use non-blocking rendering mode | +| `delegatesFocus` | When the element attempts to focus, the automatic proxy to the focus part | +| `slotAssignment` | Allow manual allocation of slot | ## Static properties diff --git a/packages/gem/docs/en/004-blog/001-create-standard-element.md b/packages/gem/docs/en/004-blog/001-create-standard-element.md index d3f78e5f..0d4b6fac 100644 --- a/packages/gem/docs/en/004-blog/001-create-standard-element.md +++ b/packages/gem/docs/en/004-blog/001-create-standard-element.md @@ -177,16 +177,19 @@ When users use custom elements, they can use the `role`,`aria-*` attributes to s html``; ``` -Use [`ElementInternals`](https://html.spec.whatwg.org/multipage/custom-elements.html#elementinternals) to define the default semantics of custom elements, which is more convenient for users to use: +Use [`ElementInternals`](https://html.spec.whatwg.org/multipage/custom-elements.html#elementinternals) to define the default semantics of custom elements, use [`delegatesFocus`](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#delegatesfocus) or `tabIndex` focus: ```ts @customElement('my-element') class MyElement extends GemElement { constructor() { - super(); + super({ delegatesFocus: true }); this.internals.role = 'region'; this.internals.ariaLabel = 'my profile'; } + render() { + return html`
Focusable
`; + } } ``` diff --git a/packages/gem/docs/zh/003-api/001-gem-element.md b/packages/gem/docs/zh/003-api/001-gem-element.md index a061f4f0..dec9f30d 100644 --- a/packages/gem/docs/zh/003-api/001-gem-element.md +++ b/packages/gem/docs/zh/003-api/001-gem-element.md @@ -13,12 +13,12 @@ class GemElement extends HTMLElement { ## 构造参数 -| 名称 | 描述 | -| --------- | ---------------------- | -| `isLight` | 是否渲染成 Light DOM | -| `isAsync` | 是否使用非阻塞渲染模式 | - -_暂时不支持 [`delegatesFocus`](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow) 参数,你可以使用 [`:focus-within`](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-within)_ +| 名称 | 描述 | +| ---------------- | ------------------------------------ | +| `isLight` | 是否渲染成 Light DOM | +| `isAsync` | 是否使用非阻塞渲染模式 | +| `delegatesFocus` | 当元素尝试聚焦时自动代理到可聚焦部分 | +| `slotAssignment` | 允许手动分配插槽 | ## 静态属性 diff --git a/packages/gem/docs/zh/004-blog/001-create-standard-element.md b/packages/gem/docs/zh/004-blog/001-create-standard-element.md index 120708e3..4b9a40ed 100644 --- a/packages/gem/docs/zh/004-blog/001-create-standard-element.md +++ b/packages/gem/docs/zh/004-blog/001-create-standard-element.md @@ -177,16 +177,19 @@ html``; html``; ``` -使用 [`ElementInternals`](https://html.spec.whatwg.org/multipage/custom-elements.html#elementinternals) 可以定义自定义元素的默认语义,更方便用户来使用: +使用 [`ElementInternals`](https://html.spec.whatwg.org/multipage/custom-elements.html#elementinternals) 可以定义自定义元素的默认语义,用 [`delegatesFocus`](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#delegatesfocus) 或者 `tabIndex` 处理聚焦: ```ts @customElement('my-element') class MyElement extends GemElement { constructor() { - super(); + super({ delegatesFocus: true }); this.internals.role = 'region'; this.internals.ariaLabel = 'my profile'; } + render() { + return html`
Focusable
`; + } } ``` diff --git a/packages/gem/src/elements/base/dialog.ts b/packages/gem/src/elements/base/dialog.ts index 61b5966b..723fc33a 100644 --- a/packages/gem/src/elements/base/dialog.ts +++ b/packages/gem/src/elements/base/dialog.ts @@ -8,6 +8,8 @@ import { history } from '../../lib/history'; * 模拟 top layer:https://github.com/whatwg/html/issues/4633. * * @attr label + * @fires open + * @fires close */ @connectStore(history.store) export abstract class GemDialogBaseElement extends GemElement { diff --git a/packages/gem/src/lib/element.ts b/packages/gem/src/lib/element.ts index c6794e78..1fc70051 100644 --- a/packages/gem/src/lib/element.ts +++ b/packages/gem/src/lib/element.ts @@ -81,7 +81,9 @@ const updateSymbol = Symbol('update'); export interface GemElementOptions { isLight?: boolean; isAsync?: boolean; + // https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow#options delegatesFocus?: boolean; + slotAssignment?: 'named' | 'manual'; } export abstract class GemElement> extends HTMLElement { @@ -114,7 +116,7 @@ export abstract class GemElement> extends HTMLElemen #memoList?: EffectItem[]; #unmountCallback?: any; - constructor({ isAsync, isLight, delegatesFocus }: GemElementOptions = {}) { + constructor({ isAsync, isLight, delegatesFocus, slotAssignment }: GemElementOptions = {}) { super(); (this as any)[constructorSymbol] = true; @@ -128,7 +130,7 @@ export abstract class GemElement> extends HTMLElemen }; this.#isAsync = isAsync; - this.#renderRoot = isLight ? this : this.attachShadow({ mode: 'open', delegatesFocus }); + this.#renderRoot = isLight ? this : this.attachShadow({ mode: 'open', delegatesFocus, slotAssignment }); const { adoptedStyleSheets } = new.target; if (adoptedStyleSheets) {