diff --git a/docs/example/Marquee/demos/fixedUsage.tsx b/docs/example/Marquee/demos/fixedUsage.tsx new file mode 100644 index 00000000..71790528 --- /dev/null +++ b/docs/example/Marquee/demos/fixedUsage.tsx @@ -0,0 +1,12 @@ +/** + * title: 短文案固定 + * description: 通过 `fixed` 属性可以控制,短文案固定、长文案滚动效果。 + */ + +import { Marquee } from '@banana-ui/react'; + +export default function BasicUsage() { + const content = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; + + return ; +} diff --git a/docs/example/Marquee/index.md b/docs/example/Marquee/index.md index dc209b1c..6a6f152e 100644 --- a/docs/example/Marquee/index.md +++ b/docs/example/Marquee/index.md @@ -19,6 +19,7 @@ demo: + ## 属性 - Attributes & Properties @@ -28,6 +29,7 @@ demo: | color | 跑马灯的文本颜色 | `string` | '' | | duration | 滚动时长(单位:s) | `number` | 20 | | pauseWhenHover
(pause-when-hover) | 鼠标悬停时是否暂停 | `boolean` | false | +| fixed | 短文案固定 | `boolean` | false | ## CSS Parts diff --git a/packages/banana-react/CHANGELOG.md b/packages/banana-react/CHANGELOG.md index 68726f79..21cf5818 100644 --- a/packages/banana-react/CHANGELOG.md +++ b/packages/banana-react/CHANGELOG.md @@ -1,5 +1,12 @@ # @banana/banana-react +## 1.19.4 + +### Patch Changes + +- Updated dependencies + - @banana-ui/banana@1.19.4 + ## 1.19.3 ### Patch Changes diff --git a/packages/banana-react/package.json b/packages/banana-react/package.json index 3ab7467c..444fafea 100644 --- a/packages/banana-react/package.json +++ b/packages/banana-react/package.json @@ -1,6 +1,6 @@ { "name": "@banana-ui/react", - "version": "1.19.3", + "version": "1.19.4", "description": "React components for Banana UI", "keywords": [ "web components", diff --git a/packages/banana/CHANGELOG.md b/packages/banana/CHANGELOG.md index f7629c72..55308255 100644 --- a/packages/banana/CHANGELOG.md +++ b/packages/banana/CHANGELOG.md @@ -1,5 +1,11 @@ # banana-ui +## 1.19.4 + +### Patch Changes + +- add fixed for marquee + ## 1.19.3 ### Patch Changes diff --git a/packages/banana/package.json b/packages/banana/package.json index 6d272f02..cb214b8e 100644 --- a/packages/banana/package.json +++ b/packages/banana/package.json @@ -1,6 +1,6 @@ { "name": "@banana-ui/banana", - "version": "1.19.3", + "version": "1.19.4", "description": "An UI library of web components can be used in any framework", "keywords": [ "web components", diff --git a/packages/banana/src/marquee/index.styles.ts b/packages/banana/src/marquee/index.styles.ts index 1713474d..ec562472 100644 --- a/packages/banana/src/marquee/index.styles.ts +++ b/packages/banana/src/marquee/index.styles.ts @@ -15,12 +15,27 @@ export default [ .marquee { overflow: hidden; background-color: var(--banana-marquee-background-color); + + display: flex; + flex-direction: row; + align-items: center; } - .content { + .content-normal { + overflow: hidden; display: inline-block; + flex: 0 0 auto; white-space: nowrap; animation: marquee var(--banana-marquee-duration) linear infinite; + transform: translateX(var(--banana-marquee-width, 100%)); + } + + .content-fixed { + overflow: hidden; + display: inline-block; + flex: 0 0 auto; + white-space: nowrap; + transform: translateX(0); } @media (any-hover: hover) { @@ -31,7 +46,7 @@ export default [ @keyframes marquee { 0% { - transform: translateX(0); + transform: translateX(var(--banana-marquee-width, 100%)); } 100% { diff --git a/packages/banana/src/marquee/index.test.ts b/packages/banana/src/marquee/index.test.ts index c1572811..a6629ce4 100644 --- a/packages/banana/src/marquee/index.test.ts +++ b/packages/banana/src/marquee/index.test.ts @@ -104,4 +104,42 @@ describe('b-marquee', () => { expect(animationPlayState3).to.equal('running'); }); }); + + describe('custom fixed', () => { + it('pauses animation when content width is less than marquee width', async () => { + const element = await fixture(html``); + + // Simulate content width being less than marquee width + element._marquee!.getBoundingClientRect = () => ({ width: 100 } as DOMRect); + element._content!.getBoundingClientRect = () => ({ width: 50 } as DOMRect); + + // 强制调用私有方法 + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + (element as any)?.firstUpdated(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + (element as any)?._calculateWidth(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect((element as any)._isNormal).to.equal(false); + expect(element.style.getPropertyValue('--banana-marquee-width')).to.equal('100px'); + }); + + it('resumes animation when content width is greater than marquee width', async () => { + const element = await fixture(html``); + + // Simulate content width being greater than marquee width + element._marquee!.getBoundingClientRect = () => ({ width: 100 } as DOMRect); + element._content!.getBoundingClientRect = () => ({ width: 200 } as DOMRect); + + // 强制调用私有方法 + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + (element as any)?.firstUpdated(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + (element as any)?._calculateWidth(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect((element as any)._isNormal).to.equal(true); + expect(element.style.getPropertyValue('--banana-marquee-width')).to.equal('100px'); + }); + }); }); diff --git a/packages/banana/src/marquee/index.ts b/packages/banana/src/marquee/index.ts index db067057..a4b249a9 100644 --- a/packages/banana/src/marquee/index.ts +++ b/packages/banana/src/marquee/index.ts @@ -1,11 +1,12 @@ import { CSSResultGroup, html, LitElement, PropertyValueMap } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; +import { customElement, property, query, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import styles from './index.styles'; @customElement('b-marquee') export default class BMarquee extends LitElement { static styles?: CSSResultGroup = styles; + private resizeObserver: ResizeObserver | undefined; connectedCallback() { super.connectedCallback(); @@ -13,6 +14,8 @@ export default class BMarquee extends LitElement { disconnectedCallback() { super.disconnectedCallback(); + // 停止观察元素的尺寸变化 + this._marquee && this.resizeObserver?.unobserve(this._marquee); } @property() @@ -28,6 +31,46 @@ export default class BMarquee extends LitElement { @property({ type: Boolean, attribute: 'pause-when-hover' }) pauseWhenHover = false; + @property({ type: Boolean, reflect: true }) + fixed = false; + + // 判断是否是 normal还是fixed (fixed为true时,才生效) + @state() + _isNormal = true; + + @query('.marquee') + _marquee: HTMLDivElement | undefined; + + @query('.content') + _content: HTMLDivElement | undefined; + + firstUpdated() { + this._setBananaMarqueeWidth(); + + // 观察元素的尺寸变化 + if (this._marquee) { + this.resizeObserver = new ResizeObserver(() => this._calculateWidth()); + + this.resizeObserver?.observe(this._marquee); + } + } + + private _calculateWidth() { + // marquee的宽度变化了 重新设置marquee的宽度 + this._setBananaMarqueeWidth(); + + if (this._marquee && this._content && this.fixed) { + const marqueeWidth = this._marquee.getBoundingClientRect().width; + const contentWidth = this._content.getBoundingClientRect().width; + + this._isNormal = contentWidth > marqueeWidth; + } + } + + private _setBananaMarqueeWidth() { + this.style.setProperty('--banana-marquee-width', `${this._marquee!.getBoundingClientRect().width}px`); + } + protected willUpdate(_changedProperties: PropertyValueMap): void { if (_changedProperties.has('color')) { const color = this.color ?? ''; @@ -41,15 +84,20 @@ export default class BMarquee extends LitElement { } render() { + const marqueeClass = classMap({ + marquee: true, + 'marquee--pause-when-hover': this.pauseWhenHover, + }); + + const contentClass = classMap({ + content: true, + 'content-normal': this._isNormal, + 'content-fixed': !this._isNormal, + }); + return html` -
-
${this.content}
+
+
${this.content}
`; } diff --git a/packages/banana/src/select/index.test.ts b/packages/banana/src/select/index.test.ts index fccee0af..78ab21f2 100644 --- a/packages/banana/src/select/index.test.ts +++ b/packages/banana/src/select/index.test.ts @@ -375,7 +375,7 @@ describe('b-select', () => { element.open = true; const clearIcon = element.shadowRoot?.querySelector('.clear-icon-container'); // mouse event - clearIcon.dispatchEvent(new MouseEvent('click')); + clearIcon?.dispatchEvent(new MouseEvent('click')); expect(element.value).to.equal(''); // should close the listbox. expect(element.open).to.equal(false); @@ -390,7 +390,7 @@ describe('b-select', () => { `); multipleElement.open = true; const multipleClearIcon = multipleElement.shadowRoot?.querySelector('.clear-icon-container'); - multipleClearIcon.dispatchEvent(new MouseEvent('click')); + multipleClearIcon?.dispatchEvent(new MouseEvent('click')); expect(multipleElement.value).to.deep.equal([]); // should close the listbox. expect(multipleElement.open).to.equal(false); @@ -406,7 +406,7 @@ describe('b-select', () => { `); element.open = true; const clearIcon = element.shadowRoot?.querySelector('.clear-icon-container'); - clearIcon.dispatchEvent(new MouseEvent('click')); + clearIcon?.dispatchEvent(new MouseEvent('click')); expect(element.open).to.equal(true); }); }); @@ -553,7 +553,7 @@ describe('b-select', () => { await element.updateComplete; const clearIcon = element.shadowRoot?.querySelector('.clear-icon-container'); - clearIcon.dispatchEvent(new MouseEvent('click')); + clearIcon?.dispatchEvent(new MouseEvent('click')); await element.updateComplete; diff --git a/public/Marquee/fixedUsage.html b/public/Marquee/fixedUsage.html new file mode 100644 index 00000000..2a4f6b09 --- /dev/null +++ b/public/Marquee/fixedUsage.html @@ -0,0 +1,2 @@ + \ No newline at end of file