diff --git a/packages/duoyun-ui/docs/en/02-elements/result.md b/packages/duoyun-ui/docs/en/02-elements/result.md index f59146c5..6b00631a 100644 --- a/packages/duoyun-ui/docs/en/02-elements/result.md +++ b/packages/duoyun-ui/docs/en/02-elements/result.md @@ -2,10 +2,18 @@ ## Example - + + +```json +{ + "icon": "icons.check", + "status": "positive", + "header": "Complete!", + "description": "Do non amet sunt nostrud excepteur ut nostrud veniam aliquip commodo incididunt." +} +``` + + ## API diff --git a/packages/duoyun-ui/docs/en/02-elements/select.md b/packages/duoyun-ui/docs/en/02-elements/select.md index 92f0e1d5..e2af478d 100644 --- a/packages/duoyun-ui/docs/en/02-elements/select.md +++ b/packages/duoyun-ui/docs/en/02-elements/select.md @@ -2,10 +2,28 @@ ## Example - + + +```json +[ + { + "searchable": true, + "multiple": true, + "adder": { "text": "New" }, + "placeholder": "Please select!", + "options": [{ "label": "Option 1" }, { "label": "Option 2" }], + "@change": "(evt) => evt.target.value = evt.detail" + }, + { + "inline": true, + "adder": { "text": "New" }, + "options": [{ "label": "Option 1" }, { "label": "Option 2" }], + "@change": "(evt) => evt.target.value = evt.detail" + } +] +``` + + ## API diff --git a/packages/duoyun-ui/docs/en/02-elements/side-navigation.md b/packages/duoyun-ui/docs/en/02-elements/side-navigation.md index f62121ca..ed81ecc6 100644 --- a/packages/duoyun-ui/docs/en/02-elements/side-navigation.md +++ b/packages/duoyun-ui/docs/en/02-elements/side-navigation.md @@ -2,10 +2,28 @@ ## Example - + + +```json +{ + "style": "width: 240px;", + "items": [ + { "title": "Page 1", "hash": "#1" }, + { "title": "Page 2", "hash": "#2" }, + { "title": "Page 3", "hash": "#3" }, + { + "title": "Group 2", + "group": [ + { "title": "Route 1", "hash": "#4" }, + { "title": "Route 2", "hash": "#5" }, + { "title": "Route 3", "hash": "#6" } + ] + } + ] +} +``` + + ## API diff --git a/packages/duoyun-ui/docs/zh/02-elements/result.md b/packages/duoyun-ui/docs/zh/02-elements/result.md index f59146c5..6b00631a 100644 --- a/packages/duoyun-ui/docs/zh/02-elements/result.md +++ b/packages/duoyun-ui/docs/zh/02-elements/result.md @@ -2,10 +2,18 @@ ## Example - + + +```json +{ + "icon": "icons.check", + "status": "positive", + "header": "Complete!", + "description": "Do non amet sunt nostrud excepteur ut nostrud veniam aliquip commodo incididunt." +} +``` + + ## API diff --git a/packages/duoyun-ui/docs/zh/02-elements/select.md b/packages/duoyun-ui/docs/zh/02-elements/select.md index 3ec12f2f..4ac2d979 100644 --- a/packages/duoyun-ui/docs/zh/02-elements/select.md +++ b/packages/duoyun-ui/docs/zh/02-elements/select.md @@ -2,10 +2,28 @@ ## Example - + + +```json +[ + { + "searchable": true, + "multiple": true, + "adder": { "text": "添加" }, + "placeholder": "请选择", + "options": [{ "label": "Option 1" }, { "label": "Option 2" }], + "@change": "(evt) => evt.target.value = evt.detail" + }, + { + "inline": true, + "adder": { "text": "添加" }, + "options": [{ "label": "Option 1" }, { "label": "Option 2" }], + "@change": "(evt) => evt.target.value = evt.detail" + } +] +``` + + ## API diff --git a/packages/duoyun-ui/docs/zh/02-elements/side-navigation.md b/packages/duoyun-ui/docs/zh/02-elements/side-navigation.md index f62121ca..ed81ecc6 100644 --- a/packages/duoyun-ui/docs/zh/02-elements/side-navigation.md +++ b/packages/duoyun-ui/docs/zh/02-elements/side-navigation.md @@ -2,10 +2,28 @@ ## Example - + + +```json +{ + "style": "width: 240px;", + "items": [ + { "title": "Page 1", "hash": "#1" }, + { "title": "Page 2", "hash": "#2" }, + { "title": "Page 3", "hash": "#3" }, + { + "title": "Group 2", + "group": [ + { "title": "Route 1", "hash": "#4" }, + { "title": "Route 2", "hash": "#5" }, + { "title": "Route 3", "hash": "#6" } + ] + } + ] +} +``` + + ## API diff --git a/packages/duoyun-ui/src/elements/base/wake-lock.ts b/packages/duoyun-ui/src/elements/base/wake-lock.ts index 661f9a9f..e9e16138 100644 --- a/packages/duoyun-ui/src/elements/base/wake-lock.ts +++ b/packages/duoyun-ui/src/elements/base/wake-lock.ts @@ -2,6 +2,7 @@ import { GemElementOptions } from '@mantou/gem/lib/element'; import { logger } from '@mantou/gem/helper/logger'; +import { addListener } from '@mantou/gem/lib/utils'; import { DuoyunVisibleBaseElement } from './visible'; @@ -25,13 +26,13 @@ export function wakeLock(ele: DuoyunWakeLockBaseElement) { }; // 当页面处于非活动状态时该锁自动失效 - document.addEventListener('visibilitychange', listener); - ele.addEventListener('show', listener); - ele.addEventListener('hide', listener); + const removeListener = addListener(document, 'visibilitychange', listener); + const removeShowListener = addListener(ele, 'show', listener); + const removeHideListener = addListener(ele, 'hide', listener); return async () => { - document.removeEventListener('visibilitychange', listener); - ele.removeEventListener('show', listener); - ele.removeEventListener('hide', listener); + removeListener(); + removeShowListener(); + removeHideListener(); (await wakeLockPromise)?.release(); }; diff --git a/packages/duoyun-ui/src/elements/carousel.ts b/packages/duoyun-ui/src/elements/carousel.ts index 09b4353a..1a77a941 100644 --- a/packages/duoyun-ui/src/elements/carousel.ts +++ b/packages/duoyun-ui/src/elements/carousel.ts @@ -9,7 +9,7 @@ import { emitter, Emitter, } from '@mantou/gem/lib/decorators'; -import { createCSSSheet, css, styleMap, classMap } from '@mantou/gem/lib/utils'; +import { createCSSSheet, css, styleMap, classMap, addListener } from '@mantou/gem/lib/utils'; import { theme } from '../lib/theme'; import { icons } from '../lib/icons'; @@ -243,9 +243,9 @@ export class DuoyunCarouselElement extends GemElement { () => this.change(this.state.currentIndex), () => [this.state.currentIndex], ); - document.addEventListener('visibilitychange', this.#pageVisibleChange); + const removeListener = addListener(document, 'visibilitychange', this.#pageVisibleChange); return () => { - document.removeEventListener('visibilitychange', this.#pageVisibleChange); + removeListener(); this.#clearTimer(); }; }; diff --git a/packages/duoyun-ui/src/elements/checkbox.ts b/packages/duoyun-ui/src/elements/checkbox.ts index 0fe1b181..f770ece1 100644 --- a/packages/duoyun-ui/src/elements/checkbox.ts +++ b/packages/duoyun-ui/src/elements/checkbox.ts @@ -35,6 +35,8 @@ const style = createCSSSheet(css` box-sizing: border-box; width: 1em; aspect-ratio: 1; + /* webkit bug */ + height: 1em; background-clip: content-box; padding: 0; color: ${theme.backgroundColor}; diff --git a/packages/duoyun-ui/src/elements/color-panel.ts b/packages/duoyun-ui/src/elements/color-panel.ts index 8c054903..45a7a4f8 100644 --- a/packages/duoyun-ui/src/elements/color-panel.ts +++ b/packages/duoyun-ui/src/elements/color-panel.ts @@ -92,6 +92,10 @@ const style = createCSSSheet(css` border: 1px solid #0008; transform: translate(-50%, -50%); } + .current { + /* webkit bug */ + height: 1.2em; + } .current span { left: 50%; top: 50%; diff --git a/packages/duoyun-ui/src/elements/flow.ts b/packages/duoyun-ui/src/elements/flow.ts index 9913501a..90c6a5f1 100644 --- a/packages/duoyun-ui/src/elements/flow.ts +++ b/packages/duoyun-ui/src/elements/flow.ts @@ -1,3 +1,5 @@ +// webkit marker arrow bug: https://duoyun-ui.gemjs.org/zh/elements/flow + import { adoptedStyle, customElement, property, part, state, refobject, RefObject } from '@mantou/gem/lib/decorators'; import { html, svg, TemplateResult } from '@mantou/gem/lib/element'; import { createCSSSheet, css, styleMap, exportPartsMap } from '@mantou/gem/lib/utils'; diff --git a/packages/duoyun-ui/src/elements/input-capture.ts b/packages/duoyun-ui/src/elements/input-capture.ts index f98db479..ea9e4668 100644 --- a/packages/duoyun-ui/src/elements/input-capture.ts +++ b/packages/duoyun-ui/src/elements/input-capture.ts @@ -1,6 +1,6 @@ import { adoptedStyle, customElement, part } from '@mantou/gem/lib/decorators'; import { GemElement, html } from '@mantou/gem/lib/element'; -import { createCSSSheet, css } from '@mantou/gem/lib/utils'; +import { addListener, createCSSSheet, css } from '@mantou/gem/lib/utils'; import { theme } from '../lib/theme'; import { getDisplayKey } from '../lib/hotkeys'; @@ -72,17 +72,17 @@ export class DuoyunInputCaptureElement extends GemElement { #onPointerup = () => this.setState({ mousePosition: null }); mounted = () => { - addEventListener('keydown', this.#onKeydown, { capture: true }); - addEventListener('pointerdown', this.#onPointerdown, { capture: true }); - addEventListener('pointermove', this.#onPointermove, { capture: true }); - addEventListener('pointerup', this.#onPointerup, { capture: true }); - addEventListener('pointercancel', this.#onPointerup, { capture: true }); + const removeKeydownListener = addListener(window, 'keydown', this.#onKeydown, { capture: true }); + const removePointerdownListener = addListener(window, 'pointerdown', this.#onPointerdown, { capture: true }); + const removePointermoveListener = addListener(window, 'pointermove', this.#onPointermove, { capture: true }); + const removePointerupListener = addListener(window, 'pointerup', this.#onPointerup, { capture: true }); + const removePointercancelListener = addListener(window, 'pointercancel', this.#onPointerup, { capture: true }); () => { - removeEventListener('keydown', this.#onKeydown, { capture: true }); - removeEventListener('pointerdown', this.#onPointerdown, { capture: true }); - removeEventListener('pointermove', this.#onPointermove, { capture: true }); - removeEventListener('pointerup', this.#onPointerup, { capture: true }); - removeEventListener('pointercancel', this.#onPointerup, { capture: true }); + removeKeydownListener(); + removePointerdownListener(); + removePointermoveListener(); + removePointerupListener(); + removePointercancelListener(); }; }; diff --git a/packages/duoyun-ui/src/elements/input.ts b/packages/duoyun-ui/src/elements/input.ts index 2a3c4d99..56e69346 100644 --- a/packages/duoyun-ui/src/elements/input.ts +++ b/packages/duoyun-ui/src/elements/input.ts @@ -291,32 +291,22 @@ export class DuoyunInputElement extends GemElement { #onKeyDown = (evt: KeyboardEvent) => { const nextValue = (n: number) => String(clamp(this.#min, Number(this.value || this.min) + n, this.#max)); - const prevent = (evt: Event) => { - evt.stopPropagation(); - evt.preventDefault(); - }; if (this.type === 'number') { - hotkeys({ - up: () => { - this.change(nextValue(this.#step)); - prevent(evt); + hotkeys( + { + up: () => this.change(nextValue(this.#step)), + down: () => this.change(nextValue(-this.#step)), }, - down: () => { - this.change(nextValue(-this.#step)); - prevent(evt); - }, - })(evt); + { stopPropagation: true }, + )(evt); } - hotkeys({ - 'ctrl+z,command+z': () => { - this.#history.undo(); - prevent(evt); - }, - 'ctrl+shift+z,command+shift+z': () => { - this.#history.redo(); - prevent(evt); + hotkeys( + { + 'ctrl+z,command+z': () => this.#history.undo(), + 'ctrl+shift+z,command+shift+z': () => this.#history.redo(), }, - })(evt); + { stopPropagation: true }, + )(evt); }; mounted = () => { diff --git a/packages/duoyun-ui/src/elements/keyboard-access.ts b/packages/duoyun-ui/src/elements/keyboard-access.ts index b9cc85e0..d96c8c66 100644 --- a/packages/duoyun-ui/src/elements/keyboard-access.ts +++ b/packages/duoyun-ui/src/elements/keyboard-access.ts @@ -1,6 +1,6 @@ import { GemElement, html } from '@mantou/gem/lib/element'; -import { adoptedStyle, customElement, attribute, part } from '@mantou/gem/lib/decorators'; -import { createCSSSheet, css, styleMap } from '@mantou/gem/lib/utils'; +import { adoptedStyle, customElement, attribute, part, property } from '@mantou/gem/lib/decorators'; +import { addListener, createCSSSheet, css, styleMap } from '@mantou/gem/lib/utils'; import { logger } from '@mantou/gem/helper/logger'; import { hotkeys, HotKeyHandles, unlock } from '../lib/hotkeys'; @@ -67,26 +67,22 @@ export class DuoyunKeyboardAccessElement extends GemElement { @part static kbd: string; @attribute activekey: string; + @property scrollContainer?: HTMLElement; + get #activeKey() { return this.activekey || 'f'; } + get #container() { + return this.scrollContainer || document.body; + } + state: State = { active: false, waiting: false, keydownHandles: {}, }; - #preventEvent = (evt: Event) => { - evt.stopPropagation(); - evt.preventDefault(); - }; - - #handler = (evt: KeyboardEvent) => { - hotkeys(this.state.keydownHandles)(evt); - this.#preventEvent(evt); - }; - #isInputTarget(evt: Event) { const originElement = evt.composedPath()[0] as HTMLElement; if ( @@ -114,6 +110,7 @@ export class DuoyunKeyboardAccessElement extends GemElement { let index = 0; const keydownHandles: HotKeyHandles = { + esc: this.#onInactive, onLock: () => this.setState({ waiting: true }), onUnlock: () => this.setState({ waiting: false }), onUncapture: () => logger.warn('Un Capture!'), @@ -151,7 +148,7 @@ export class DuoyunKeyboardAccessElement extends GemElement { const key = getChars(index); if (!key) return; // `a-b` - keydownHandles[[...key].join('-')] = (evt: KeyboardEvent) => { + keydownHandles[[...key].join('-')] = () => { this.setState({ active: false }); if ('showPicker' in element) { (element as HTMLInputElement).showPicker(); @@ -159,20 +156,17 @@ export class DuoyunKeyboardAccessElement extends GemElement { element.focus(); element.click(); } - this.#preventEvent(evt); }; index++; return { key, top, left }; }) .filter(isNotNullish), }); - this.#preventEvent(evt); }; - #onInactive = (evt: KeyboardEvent) => { + #onInactive = () => { if (!this.state.active) return; this.setState({ active: false }); - this.#preventEvent(evt); }; #onCancel = () => { @@ -182,15 +176,18 @@ export class DuoyunKeyboardAccessElement extends GemElement { }; #onKeydown = (evt: KeyboardEvent) => { + if (this.state.active) return; if (this.#isInputTarget(evt)) return; - hotkeys({ - [this.#activeKey]: this.#onActive, - j: () => document.body.scrollBy(0, -innerHeight / 3), - k: () => document.body.scrollBy(0, innerHeight / 3), - h: () => document.body.scrollBy(0, -innerHeight), - l: () => document.body.scrollBy(0, innerHeight), - esc: this.#onInactive, - })(evt); + hotkeys( + { + [this.#activeKey]: this.#onActive, + j: () => this.#container.scrollBy(0, -innerHeight / 3), + k: () => this.#container.scrollBy(0, innerHeight / 3), + h: () => this.#container.scrollBy(0, -innerHeight), + l: () => this.#container.scrollBy(0, innerHeight), + }, + { stopPropagation: true }, + )(evt); }; mounted = () => { @@ -198,20 +195,25 @@ export class DuoyunKeyboardAccessElement extends GemElement { () => { if (this.state.active) { document.body.style.pointerEvents = 'none'; - addEventListener('keydown', this.#handler, { capture: true }); + const removeListener = addListener( + window, + 'keydown', + hotkeys(this.state.keydownHandles, { stopPropagation: true }), + { capture: true }, + ); return () => { document.body.style.pointerEvents = 'auto'; - removeEventListener('keydown', this.#handler, { capture: true }); + removeListener(); }; } }, () => [this.state.active], ); - addEventListener('keydown', this.#onKeydown, { capture: true }); - addEventListener('pointerdown', this.#onCancel); + const removeKeydownListener = addListener(window, 'keydown', this.#onKeydown, { capture: true }); + const removePointerdown = addListener(window, 'pointerdown', this.#onCancel); return () => { - removeEventListener('keydown', this.#onKeydown, { capture: true }); - removeEventListener('pointerdown', this.#onCancel); + removeKeydownListener(); + removePointerdown(); }; }; diff --git a/packages/duoyun-ui/src/elements/list.ts b/packages/duoyun-ui/src/elements/list.ts index 4d986b3c..ec3957c5 100644 --- a/packages/duoyun-ui/src/elements/list.ts +++ b/packages/duoyun-ui/src/elements/list.ts @@ -12,7 +12,7 @@ import { boolattribute, } from '@mantou/gem/lib/decorators'; import { GemElement, html, TemplateResult } from '@mantou/gem/lib/element'; -import { createCSSSheet, css, LinkedList, LinkedListItem, styled, styleMap } from '@mantou/gem/lib/utils'; +import { addListener, createCSSSheet, css, LinkedList, LinkedListItem, styled, styleMap } from '@mantou/gem/lib/utils'; import { logger } from '@mantou/gem/helper/logger'; import { theme } from '../lib/theme'; @@ -484,10 +484,8 @@ export class DuoyunListElement extends GemElement { if (this.#initState) this.scrollContainer.scrollTo(0, this.#initState.scrollTop); this.effect(() => { - this.infinite && this.scrollContainer.addEventListener('scroll', this.#onScroll); - return () => { - this.scrollContainer.removeEventListener('scroll', this.#onScroll); - }; + if (!this.infinite) return; + return addListener(this.scrollContainer, 'scroll', this.#onScroll); }); // 已经等到新值需要检查 diff --git a/packages/duoyun-ui/src/elements/modal.ts b/packages/duoyun-ui/src/elements/modal.ts index ca30e5ae..899737b4 100644 --- a/packages/duoyun-ui/src/elements/modal.ts +++ b/packages/duoyun-ui/src/elements/modal.ts @@ -14,7 +14,7 @@ import { slot, } from '@mantou/gem/lib/decorators'; import { GemElement, html, TemplateResult } from '@mantou/gem/lib/element'; -import { createCSSSheet, css, styled } from '@mantou/gem/lib/utils'; +import { addListener, createCSSSheet, css, styled } from '@mantou/gem/lib/utils'; import { mediaQuery } from '@mantou/gem/helper/mediaquery'; import { theme } from '../lib/theme'; @@ -282,8 +282,7 @@ export class DuoyunModalElement extends GemElement { }, () => [this.open], ); - this.addEventListener('keydown', this.#keydown); - return () => this.removeEventListener('keydown', this.#keydown); + return addListener(this, 'keydown', this.#keydown); }; render = () => { diff --git a/packages/duoyun-ui/src/elements/page-loadbar.ts b/packages/duoyun-ui/src/elements/page-loadbar.ts index 55a68c54..e019c068 100644 --- a/packages/duoyun-ui/src/elements/page-loadbar.ts +++ b/packages/duoyun-ui/src/elements/page-loadbar.ts @@ -43,7 +43,7 @@ export class DuoyunPageLoadbarElement extends GemElement { clearInterval(Loadbar.timer); Loadbar.timer = window.setTimeout(() => { const instance = Loadbar.instance || new Loadbar(); - document.body.append(instance); + if (!instance.isConnected) document.body.append(instance); instance.setState({ progress: 0 }); Loadbar.timer = window.setInterval(() => { instance.setState({ progress: instance.state.progress + (95 - instance.state.progress) * 0.1 }); diff --git a/packages/duoyun-ui/src/elements/popover.ts b/packages/duoyun-ui/src/elements/popover.ts index 6f482739..5e8b92bd 100644 --- a/packages/duoyun-ui/src/elements/popover.ts +++ b/packages/duoyun-ui/src/elements/popover.ts @@ -10,7 +10,7 @@ import { RefObject, } from '@mantou/gem/lib/decorators'; import { GemElement, html, TemplateResult } from '@mantou/gem/lib/element'; -import { createCSSSheet, css, styleMap, StyleObject } from '@mantou/gem/lib/utils'; +import { addListener, createCSSSheet, css, styleMap, StyleObject } from '@mantou/gem/lib/utils'; import { toggleActiveState, getAssignedElements, getBoundingClientRect } from '../lib/element'; import { sleep, setBodyInert } from '../lib/utils'; @@ -466,7 +466,6 @@ export class DuoyunPopoverGhostElement extends GemElement { #onKeyDown = hotkeys({ esc: () => this.close(null) }); mounted = () => { - addEventListener('keydown', this.#onKeyDown); - return () => removeEventListener('keydown', this.#onKeyDown); + return addListener(window, 'keydown', this.#onKeyDown); }; } diff --git a/packages/duoyun-ui/src/elements/select.ts b/packages/duoyun-ui/src/elements/select.ts index 004647f3..a3ddc136 100644 --- a/packages/duoyun-ui/src/elements/select.ts +++ b/packages/duoyun-ui/src/elements/select.ts @@ -194,75 +194,79 @@ export class DuoyunSelectElement extends GemElement implements BasePicker search: '', }; - #open = () => { + #open = async () => { if (this.disabled) return; + if (this.state.open) return; this.setState({ open: true }); + // safari auto focus + await Promise.resolve(); this.searchRef.element?.focus(); }; #close = () => { + if (!this.state.open) return; this.setState({ open: false }); }; - #onKeydown = hotkeys({ - esc: (evt) => { - if (this.state.open) { - this.#close(); - if (!this.inline) { - evt.stopPropagation(); - } - } - }, - 'space,enter': (evt) => { - this.#open(); - this.searchRef.element?.focus(); - evt.preventDefault(); - }, - }); + #onKeydown = (evt: KeyboardEvent) => { + if (this.inline) return; + hotkeys( + { + esc: this.#close, + 'space,enter': this.#open, + }, + { stopPropagation: true }, + )(evt); + }; - #onEsc = (evt: KeyboardEvent) => { + #onEsc = () => { this.#close(); (this.searchRef.element || this).focus(); - evt.stopPropagation(); }; - #onSearchKeydown = hotkeys({ - backspace: () => { - if (!this.state.search && this.#valueOptions?.length) { - if (this.multiple) { - this.#onRemoveTag(this.#valueOptions[this.#valueOptions.length - 1]); - } else { - this.change(undefined); + #onSearchKeydown = hotkeys( + { + backspace: () => { + if (!this.state.search && this.#valueOptions?.length) { + if (this.multiple) { + this.#onRemoveTag(this.#valueOptions[this.#valueOptions.length - 1]); + } else { + this.change(undefined); + } } - } - }, - tab: (evt) => { - this.optionsRef.element?.focus(); - evt.preventDefault(); - }, - esc: this.#onEsc, - enter: (evt) => { - const options = this.#filteredOptions; - if (options?.length === 1) { - const { value, label } = options[0]; - this.#onChange(value ?? label); - } - evt.stopPropagation(); - }, - space: (evt) => { - evt.stopPropagation(); + }, + tab: (evt) => { + if (!this.state.open) return; + this.optionsRef.element?.focus(); + evt.preventDefault(); + }, + esc: this.#onEsc, + enter: () => { + const options = this.#filteredOptions; + if (options?.length === 1) { + const { value, label } = options[0]; + this.#onChange(value ?? label); + } + }, + space: () => { + // Used hotkey options + }, }, - }); + { preventDefault: false, stopPropagation: true }, + ); - #onOptionsKeydown = hotkeys({ - esc: this.#onEsc, - 'shift+tab': (evt) => { - if (!this.optionsRef.element?.shadowRoot?.activeElement) { - (this.searchRef.element || this).focus(); - evt.preventDefault(); - } + #onOptionsKeydown = hotkeys( + { + esc: this.#onEsc, + 'shift+tab': (evt) => { + if (!this.optionsRef.element?.shadowRoot?.activeElement) { + (this.searchRef.element || this).focus(); + evt.preventDefault(); + } + }, }, - }); + { preventDefault: false, stopPropagation: true }, + ); #onSearch = (evt: CustomEvent) => { this.setState({ search: evt.detail, open: true }); diff --git a/packages/duoyun-ui/src/elements/selection-box.ts b/packages/duoyun-ui/src/elements/selection-box.ts index 9bb3ba5b..06b461e8 100644 --- a/packages/duoyun-ui/src/elements/selection-box.ts +++ b/packages/duoyun-ui/src/elements/selection-box.ts @@ -78,6 +78,8 @@ export class DuoyunSelectionBoxElement extends GemElement { }; #onPointerMove = (evt: PointerEvent) => { + // disabled text select + evt.preventDefault(); const start = this.state.start!; const x = evt.x === start[0] ? evt.x + 0.01 : evt.x; const y = evt.y === start[1] ? evt.y + 0.01 : evt.y; diff --git a/packages/duoyun-ui/src/elements/slider.ts b/packages/duoyun-ui/src/elements/slider.ts index 87c68894..d337c429 100644 --- a/packages/duoyun-ui/src/elements/slider.ts +++ b/packages/duoyun-ui/src/elements/slider.ts @@ -34,6 +34,9 @@ const style = createCSSSheet(css` align-items: center; gap: 1em; block-size: calc(2.2em + 2px); + border-radius: ${theme.normalRound}; + box-sizing: border-box; + padding-inline: 2px; } :host([orientation='vertical']) { writing-mode: vertical-lr; @@ -185,8 +188,8 @@ export class DuoyunSliderElement extends GemElement { }; #onKeydown = hotkeys({ - up: () => this.#setPrecisionValue(this.value + 1), - down: () => this.#setPrecisionValue(this.value - 1), + up: () => this.#setPrecisionValue(this.value + this.#step), + down: () => this.#setPrecisionValue(this.value - this.#step), }); #onInputChange = (evt: CustomEvent) => { @@ -231,6 +234,7 @@ export class DuoyunSliderElement extends GemElement { ?disabled=${this.disabled} class="input" type="number" + step=${this.step} value=${String(this.value)} @change=${this.#onInputChange} > diff --git a/packages/duoyun-ui/src/elements/timeline.ts b/packages/duoyun-ui/src/elements/timeline.ts index 74edaeec..0154e082 100644 --- a/packages/duoyun-ui/src/elements/timeline.ts +++ b/packages/duoyun-ui/src/elements/timeline.ts @@ -14,6 +14,7 @@ const style = createCSSSheet(css` --size: 1.5em; line-height: var(--size); display: block; + overscroll-behavior: auto; } .item { position: relative; diff --git a/packages/duoyun-ui/src/elements/toast.ts b/packages/duoyun-ui/src/elements/toast.ts index 20c6b8cc..7a9a8e8f 100644 --- a/packages/duoyun-ui/src/elements/toast.ts +++ b/packages/duoyun-ui/src/elements/toast.ts @@ -69,6 +69,7 @@ const style = createCSSSheet(css` background: ${theme.negativeColor}; } .icon { + flex-shrink: 0; width: var(--item-icon-height); } .body { @@ -109,10 +110,10 @@ export class DuoyunToastElement extends GemElement { static open(type: Type, content: string | TemplateResult, { debug, duration = 3000 }: ToastOptions = {}) { const toast = Toast.instance || new Toast(); - document.body.append(toast); + if (!toast.isConnected) document.body.append(toast); const key = type + getStringFromTemplate(content); const item = toast.items?.find((e) => e.key === key) || { key, type, content }; - // 如何 item 正在执行删除动画,这里会导致一点小瑕疵 + // 如果 item 正在执行删除动画,这里会导致一点小瑕疵 toast.items = [...(toast.items || []).filter((e) => e !== item), item]; // 取消正在执行移除动画的删除定时器 removedSet.delete(item); diff --git a/packages/duoyun-ui/src/lib/hotkeys.ts b/packages/duoyun-ui/src/lib/hotkeys.ts index 3302d138..a67fb496 100644 --- a/packages/duoyun-ui/src/lib/hotkeys.ts +++ b/packages/duoyun-ui/src/lib/hotkeys.ts @@ -208,12 +208,18 @@ export function unlock() { unlockCallback.clear(); } +type HotkeysOptions = { + stopPropagation?: boolean; + preventDefault?: boolean; +}; + /** * Must have non-control character; * Not case sensitive; * Support `a-b`, press `a`, hotkeys be locked, wait next `keydown` event, allow call `unlock` */ -export function hotkeys(handles: HotKeyHandles) { +export function hotkeys(handles: HotKeyHandles, options: HotkeysOptions = {}) { + const { stopPropagation, preventDefault = true } = options; return function (evt: KeyboardEvent) { if (evt.isComposing) return; if (locked) return; @@ -229,6 +235,8 @@ export function hotkeys(handles: HotKeyHandles) { const matchResult = shortcuts.map((hotkey) => matchHotKey(evt, hotkey)); if (matchResult.some((r) => r === true)) { captured = true; + if (preventDefault) evt.preventDefault(); + if (stopPropagation) evt.stopPropagation(); handle(evt); } matchResult.filter(isNotBoolean).forEach((key) => { @@ -274,12 +282,6 @@ export function hotkeys(handles: HotKeyHandles) { * Support space,enter */ export const commonHandle = hotkeys({ - 'space,enter': (evt) => { - (evt.target as HTMLElement).click(); - evt.preventDefault(); - }, - esc: (evt) => { - (evt.target as HTMLElement).blur(); - evt.preventDefault(); - }, + 'space,enter': (evt) => (evt.target as HTMLElement).click(), + esc: (evt) => (evt.target as HTMLElement).blur(), }); diff --git a/packages/gem-book/package.json b/packages/gem-book/package.json index 85f14c5a..5a124e9b 100644 --- a/packages/gem-book/package.json +++ b/packages/gem-book/package.json @@ -1,6 +1,6 @@ { "name": "gem-book", - "version": "1.5.32", + "version": "1.5.33", "description": "Create your document website easily and quickly", "keywords": [ "doc", diff --git a/packages/gem/src/elements/base/gesture.ts b/packages/gem/src/elements/base/gesture.ts index 24ffba80..db66f265 100644 --- a/packages/gem/src/elements/base/gesture.ts +++ b/packages/gem/src/elements/base/gesture.ts @@ -202,7 +202,12 @@ export class GemGestureElement extends GemElement { #onMoveSet = (evt: PointerEvent) => { evt.stopPropagation(); - evt.getCoalescedEvents().forEach((evt) => this.#onMove(evt)); + // https://bugs.webkit.org/show_bug.cgi?id=210454 + if ('getCoalescedEvents' in evt) { + evt.getCoalescedEvents().forEach((evt) => this.#onMove(evt)); + } else { + this.#onMove(evt); + } }; #onEnd = (evt: PointerEvent) => {