diff --git a/packages/uui-boolean-input/lib/uui-boolean-input.element.ts b/packages/uui-boolean-input/lib/uui-boolean-input.element.ts index fd1ce1eaa..86000050a 100644 --- a/packages/uui-boolean-input/lib/uui-boolean-input.element.ts +++ b/packages/uui-boolean-input/lib/uui-boolean-input.element.ts @@ -114,14 +114,14 @@ export abstract class UUIBooleanInputElement extends UUIFormControlMixin( this._value = 'on'; } this.inputRole = inputRole; - this.addEventListener('keypress', this._onKeypress); + this.addEventListener('keydown', this.#onKeyDown); } protected getFormElement(): HTMLInputElement { return this._input; } - private _onKeypress(e: KeyboardEvent): void { + #onKeyDown(e: KeyboardEvent): void { if (e.key == 'Enter') { this.submit(); } diff --git a/packages/uui-boolean-input/lib/uui-boolean-input.test.ts b/packages/uui-boolean-input/lib/uui-boolean-input.test.ts index 2a81274e7..45935baa9 100644 --- a/packages/uui-boolean-input/lib/uui-boolean-input.test.ts +++ b/packages/uui-boolean-input/lib/uui-boolean-input.test.ts @@ -128,8 +128,8 @@ describe('BooleanInputBaseElement in a Form', () => { describe('submit', () => { it('should submit when pressing enter', async () => { - const listener = oneEvent(formElement, 'submit', false); - element.dispatchEvent(new KeyboardEvent('keypress', { key: 'Enter' })); + const listener = oneEvent(formElement, 'submit'); + element.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); const event = await listener; expect(event).to.exist; diff --git a/packages/uui-card-content-node/lib/uui-card-content-node.element.ts b/packages/uui-card-content-node/lib/uui-card-content-node.element.ts index e0bc56d86..80658db04 100644 --- a/packages/uui-card-content-node/lib/uui-card-content-node.element.ts +++ b/packages/uui-card-content-node/lib/uui-card-content-node.element.ts @@ -24,11 +24,30 @@ export class UUICardContentNodeElement extends UUICardElement { @property({ type: String }) name = ''; + /** + * Node details + * @type {string} + * @attr + * @default '' + */ + @property({ type: String }) + detail = ''; + @state() private _iconSlotHasContent = false; - protected fallbackIcon = - ''; + protected fallbackIcon = ` + + + `; private _onSlotIconChange(event: Event) { this._iconSlotHasContent = @@ -41,18 +60,37 @@ export class UUICardContentNodeElement extends UUICardElement { return html``; } + protected renderDetail() { + return html`${this.detail}`; + } + + #renderContent() { + return html` + + + + + ${this._iconSlotHasContent === false + ? this._renderFallbackIcon() + : ''} + +
${this.name}
+
+ ${this.renderDetail()} +
+ `; + } + #renderButton() { - return html`
- - - ${this._iconSlotHasContent === false ? this._renderFallbackIcon() : ''} - - ${this.name} -
`; + ${this.#renderContent()} + `; } #renderLink() { @@ -67,11 +105,7 @@ export class UUICardContentNodeElement extends UUICardElement { this.target === '_blank' ? 'noopener noreferrer' : undefined, ), )}> - - - ${this._iconSlotHasContent === false ? this._renderFallbackIcon() : ''} - - ${this.name} + ${this.#renderContent()} `; } @@ -81,7 +115,6 @@ export class UUICardContentNodeElement extends UUICardElement {
- `; @@ -94,7 +127,6 @@ export class UUICardContentNodeElement extends UUICardElement { min-width: 250px; flex-direction: column; justify-content: space-between; - padding: var(--uui-size-3) var(--uui-size-4); } slot[name='tag'] { @@ -132,30 +164,57 @@ export class UUICardContentNodeElement extends UUICardElement { line-height: calc(2 * var(--uui-size-3)); } - #icon { - font-size: 1.2em; - margin-right: var(--uui-size-1); - } - #open-part { display: flex; position: relative; font-weight: 700; align-items: center; cursor: pointer; + flex-grow: 1; + padding: var(--uui-size-space-4) var(--uui-size-space-5); + } + + #content { + align-self: stretch; + display: flex; + flex-direction: column; } - :host([disabled]) #open-part { + #item { + position: relative; + display: flex; + align-self: stretch; + line-height: normal; + align-items: center; + margin-top: var(--uui-size-1); + } + + #icon { + font-size: 1.2em; + margin-right: var(--uui-size-1); + } + + :host([selectable]) #open-part { + padding: 0; + margin: var(--uui-size-space-4) var(--uui-size-space-5); + } + + :host([disabled]) #name { pointer-events: none; } - #open-part:hover { + :host(:not([disabled])) #open-part:hover #icon { + color: var(--uui-color-interactive-emphasis); + } + :host(:not([disabled])) #open-part:hover #name { text-decoration: underline; color: var(--uui-color-interactive-emphasis); } - - #name { - margin-top: 4px; + :host(:not([disabled])) #open-part:hover #detail { + color: var(--uui-color-interactive-emphasis); + } + :host(:not([disabled])) #open-part:hover #default { + color: var(--uui-color-interactive-emphasis); } `, ]; diff --git a/packages/uui-card-media/lib/uui-card-media.element.ts b/packages/uui-card-media/lib/uui-card-media.element.ts index a1b625a89..f950356ae 100644 --- a/packages/uui-card-media/lib/uui-card-media.element.ts +++ b/packages/uui-card-media/lib/uui-card-media.element.ts @@ -23,6 +23,15 @@ export class UUICardMediaElement extends UUICardElement { @property({ type: String }) name = ''; + /** + * Media detail + * @type {string} + * @attr detail + * @default '' + */ + @property({ type: String }) + detail?: string; + /** * Media file extension, without "." * @type {string} @@ -68,15 +77,7 @@ export class UUICardMediaElement extends UUICardElement { tabindex=${this.disabled ? (nothing as any) : '0'} @click=${this.handleOpenClick} @keydown=${this.handleOpenKeydown}> - - ${this.name} + ${this.#renderContent()} `; } @@ -94,16 +95,20 @@ export class UUICardMediaElement extends UUICardElement { this.target === '_blank' ? 'noopener noreferrer' : undefined, ), )}> + ${this.#renderContent()} + + `; + } + + #renderContent() { + return html` +
- ${this.name} - + ${this.name} + ${this.detail} +
`; } @@ -162,28 +167,18 @@ export class UUICardMediaElement extends UUICardElement { #open-part { position: absolute; - bottom: 0; - width: 100%; - background-color: var(--uui-color-surface); + z-index: 1; + inset: 0; color: var(--uui-color-interactive); border: none; cursor: pointer; - border-top: 1px solid var(--uui-color-divider); - border-radius: 0 0 var(--uui-border-radius) var(--uui-border-radius); display: flex; - justify-content: flex-start; - align-items: center; - font-family: inherit; - font-size: var(--uui-type-small-size); - box-sizing: border-box; - padding: var(--uui-size-2) var(--uui-size-4); - text-align: left; - word-break: break-word; + flex-direction: column; + justify-content: flex-end; } :host([disabled]) #open-part { pointer-events: none; - background: var(--uui-color-disabled); color: var(--uui-color-contrast-disabled); } @@ -197,6 +192,35 @@ export class UUICardMediaElement extends UUICardElement { opacity: 0; } + #content { + position: relative; + display: flex; + width: 100%; + align-items: center; + font-family: inherit; + font-size: var(--uui-type-small-size); + box-sizing: border-box; + text-align: left; + word-break: break-word; + padding-top: var(--uui-size-space-3); + } + + #content::before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + border-top: 1px solid var(--uui-color-divider); + border-radius: 0 0 var(--uui-border-radius) var(--uui-border-radius); + background-color: var(--uui-color-surface); + pointer-events: none; + opacity: 0.96; + } + + #name { + font-weight: 700; + } + :host( [image]:not([image='']):hover, [image]:not([image='']):focus, @@ -210,6 +234,18 @@ export class UUICardMediaElement extends UUICardElement { transition-delay: 0s; } + :host([selectable]) #open-part { + inset: var(--uui-size-space-3) var(--uui-size-space-4); + } + :host(:not([selectable])) #content { + padding: var(--uui-size-space-3) var(--uui-size-space-4); + } + :host([selectable]) #content::before { + inset: calc(var(--uui-size-space-3) * -1) + calc(var(--uui-size-space-4) * -1); + top: 0; + } + /* #info-icon { margin-right: var(--uui-size-2); diff --git a/packages/uui-card-user/lib/uui-card-user.element.ts b/packages/uui-card-user/lib/uui-card-user.element.ts index e6dc71267..226b5e021 100644 --- a/packages/uui-card-user/lib/uui-card-user.element.ts +++ b/packages/uui-card-user/lib/uui-card-user.element.ts @@ -44,7 +44,7 @@ export class UUICardUserElement extends UUICardElement { tabindex=${this.disabled ? (nothing as any) : '0'} @click=${this.handleOpenClick} @keydown=${this.handleOpenKeydown}> - ${this.name} + ${this.#renderContent()} `; } @@ -60,24 +60,32 @@ export class UUICardUserElement extends UUICardElement { this.target === '_blank' ? 'noopener noreferrer' : undefined, ), )}> - ${this.name} + ${this.#renderContent()} `; } - public render() { - return html` + #renderContent() { + return html`
${this._avatarSlotHasContent ? nothing : html``} - ${this.href ? this.#renderLink() : this.#renderButton()} + ${this.name} +
`; + } + + public render() { + return html` + ${this.href ? this.#renderLink() : this.#renderButton()} + +
`; @@ -88,10 +96,6 @@ export class UUICardUserElement extends UUICardElement { css` :host { min-width: 250px; - flex-direction: column; - justify-content: space-between; - padding: var(--uui-size-3); - align-items: center; } slot:not([name])::slotted(*) { @@ -113,8 +117,8 @@ export class UUICardUserElement extends UUICardElement { slot[name='actions'] { position: absolute; - top: var(--uui-size-4); - right: var(--uui-size-4); + top: var(--uui-size-space-4); + right: var(--uui-size-space-4); display: flex; justify-content: right; @@ -127,35 +131,46 @@ export class UUICardUserElement extends UUICardElement { opacity: 1; } - #avatar { - margin: var(--uui-size-3); + #open-part { + cursor: pointer; + flex-grow: 1; + padding: var(--uui-size-space-5) var(--uui-size-space-4); } - slot[name='icon']::slotted(*) { - font-size: 1.2em; + :host([disabled]) #open-part { + pointer-events: none; } - #open-part { + #open-part:hover #content { + color: var(--uui-color-interactive-emphasis); + } + #open-part:hover #content > span { + text-decoration: underline; + } + + :host([selectable]) #open-part { + padding: 0; + margin: var(--uui-size-space-5) var(--uui-size-space-4); + } + + #content { display: flex; + flex-direction: column; position: relative; - font-weight: 700; align-items: center; - cursor: pointer; margin: 0 0 3px 0; } - :host([disabled]) #open-part { - pointer-events: none; - } - - #open-part > span { + #content > span { vertical-align: center; margin-top: 3px; + font-weight: 700; } - #open-part:hover { - text-decoration: underline; - color: var(--uui-color-interactive-emphasis); + .avatar { + font-size: 1.5em; + margin-top: var(--uui-size-space-1); + margin-bottom: var(--uui-size-space-2); } `, ]; diff --git a/packages/uui-card/lib/uui-card.element.ts b/packages/uui-card/lib/uui-card.element.ts index 197dfc90f..f1e61443f 100644 --- a/packages/uui-card/lib/uui-card.element.ts +++ b/packages/uui-card/lib/uui-card.element.ts @@ -102,24 +102,18 @@ export class UUICardElement extends SelectOnlyMixin( transition: box-shadow 100ms ease-out; } - :host(*) { - /* TODO: implement globally shared outline style */ + :host([selectable]:focus-visible) { outline-color: var(--uui-color-focus); - outline-offset: 4px; + outline-width: var(--uui-card-border-width); + outline-style: solid; + outline-offset: var(--uui-card-border-width); } - :host(*) * { + :host() * { /* TODO: implement globally shared outline style */ outline-color: var(--uui-color-focus); } - :host(:focus) { - outline-color: var(--uui-color-focus); - outline-width: var(--uui-card-border-width); - outline-style: solid; - outline-offset: var(--uui-card-border-width); - } - :host([error])::before { content: ''; position: absolute; @@ -133,48 +127,110 @@ export class UUICardElement extends SelectOnlyMixin( border-radius: var(--uui-border-radius); } - :host([selectable]) { - cursor: pointer; + button { + font-size: inherit; + font-family: inherit; + border: 0; + padding: 0; + background-color: transparent; + text-align: left; + color: var(--uui-color-text); } - :host([disabled]) { - background: var(--uui-color-disabled); - color: var(--uui-color-disabled-contrast); + a { + text-decoration: none; + color: inherit; } - :host([selectable])::after { - content: ''; + button:focus, + a:focus { + outline-color: var(--uui-color-focus); + outline-width: var(--uui-card-border-width); + outline-style: solid; + outline-offset: var(--uui-card-border-width); + border-radius: var(--uui-border-radius); + } + + :host([selectable]) { + cursor: pointer; + } + :host([selectable]) #select-border { position: absolute; + z-index: 2; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; pointer-events: none; - inset: calc(var(--uui-card-border-width) * -1); - width: calc(100% + calc(var(--uui-card-border-width) * 2)); - height: calc(100% + calc(var(--uui-card-border-width) * 2)); - box-sizing: border-box; - border: var(--uui-card-border-width) solid var(--uui-color-selected); - border-radius: calc( - var(--uui-border-radius) + var(--uui-card-border-width) - ); - transition: opacity 100ms ease-out; opacity: 0; + transition: opacity 120ms; + } + :host([selectable]) #select-border::after { + content: ''; + position: absolute; + width: 100%; + height: 100%; + box-sizing: border-box; + border: 2px solid var(--uui-color-selected); + border-radius: calc(var(--uui-border-radius) + 2px); + box-shadow: + 0 0 4px 0 var(--uui-color-selected), + inset 0 0 2px 0 var(--uui-color-selected); + } + :host([selected]) #select-border { + opacity: 1; } - :host([selectable]:hover)::after { + :host([selectable]:not([selected]):hover) #select-border { opacity: 0.33; } - :host([selectable][selected]:hover)::after { - opacity: 0.66; + :host([selectable][selected]:hover) #select-border { + opacity: 0.8; + } + + :host([selectable]:not([selected])) #open-part:hover + #select-border { + opacity: 0; } - :host([selectable][selected])::after { + :host([selectable][selected]) #open-part:hover + #select-border { opacity: 1; } + :host([selectable]:not([selected]):hover) #select-border::after { + animation: not-selected--hover 1.2s infinite; + } + @keyframes not-selected--hover { + 0%, + 100% { + opacity: 0.66; + } + 50% { + opacity: 1; + } + } + + :host([selectable][selected]:hover) #select-border::after { + animation: selected--hover 1.4s infinite; + } + @keyframes selected--hover { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.66; + } + } + :host([selectable]) #open-part:hover + #select-border::after { + animation: none; + } + :host([select-only]) *, :host([select-only]) ::slotted(*) { pointer-events: none; } - a { - text-decoration: none; - color: inherit; + :host([disabled]) { + background: var(--uui-color-disabled); + color: var(--uui-color-disabled-contrast); } `, ]; diff --git a/packages/uui-css/lib/uui-text.css b/packages/uui-css/lib/uui-text.css index 364dd1bfd..9fdd33eab 100644 --- a/packages/uui-css/lib/uui-text.css +++ b/packages/uui-css/lib/uui-text.css @@ -1,3 +1,15 @@ +.uui-h1, +.uui-h2, +.uui-h3, +.uui-h4, +.uui-h5, +.uui-a, +.uui-p, +.uui-p-lead, +.uui-small, +.uui-quoteblock, +.uui-ul, +.uui-ol, .uui-text { font-family: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 15px; @@ -6,7 +18,7 @@ } .uui-text h1, -.uui-text .uui-h1 { +.uui-h1 { font-size: var(--uui-type-h1-size); line-height: var(--uui-size-layout-4); font-weight: 300; @@ -20,13 +32,13 @@ } .uui-text h1.--no-top-margin, .uui-text h1:first-child, -.uui-text .uui-h1.--no-top-margin, -.uui-text .uui-h1:first-child { +.uui-h1.--no-top-margin, +.uui-h1:first-child { margin-top: 0; } .uui-text h2, -.uui-text .uui-h2 { +.uui-h2 { font-size: var(--uui-type-h2-size); line-height: var(--uui-size-layout-3); font-weight: 300; @@ -40,13 +52,13 @@ } .uui-text h2.--no-top-margin, .uui-text h2:first-child, -.uui-text .uui-h2.--no-top-margin, -.uui-text .uui-h2:first-child { +.uui-h2.--no-top-margin, +.uui-h2:first-child { margin-top: 0; } .uui-text h3, -.uui-text .uui-h3 { +.uui-h3 { font-size: var(--uui-type-h3-size); line-height: var(--uui-size-large); font-weight: 300; @@ -56,13 +68,13 @@ } .uui-text h3.--no-top-margin, .uui-text h3:first-child, -.uui-text .uui-h3.--no-top-margin, -.uui-text .uui-h3:first-child { +.uui-h3.--no-top-margin, +.uui-h3:first-child { margin-top: 0; } .uui-text h4, -.uui-text .uui-h4 { +.uui-h4 { font-size: var(--uui-type-h4-size); line-height: 21px; font-weight: 400; @@ -72,13 +84,13 @@ } .uui-text h4.--no-top-margin, .uui-text h4:first-child, -.uui-text .uui-h4.--no-top-margin, -.uui-text .uui-h4:first-child { +.uui-h4.--no-top-margin, +.uui-h4:first-child { margin-top: 0; } .uui-text h5, -.uui-text .uui-h5 { +.uui-h5 { font-size: var(--uui-type-h5-size); line-height: inherit; font-weight: 700; @@ -89,31 +101,37 @@ .uui-text h5.--no-top-margin, .uui-text h5:first-child, -.uui-text .uui-h5.--no-top-margin, -.uui-text .uui-h5:first-child { +.uui-h5.--no-top-margin, +.uui-h5:first-child { margin-top: 0; } +.uui-p, .uui-text p { margin-top: var(--uui-size-layout-1); margin-bottom: var(--uui-size-layout-1); } +.uui-p-lead, .uui-text p.uui-lead { font-size: var(--uui-size-6); } +.uui-a, .uui-text a { color: var(--uui-color-interactive); } -.uui-text a:link, +.uui-a:link, +.uui-a:active .uui-text a:link, .uui-text a:active { color: var(--uui-color-interactive); } +.uui-a:hover, .uui-text a:hover { color: var(--uui-color-interactive-emphasis); } +.uui-small, .uui-text small { display: inline-block; font-size: var(--uui-type-small-size); @@ -121,6 +139,7 @@ margin-bottom: var(--uui-size-layout-1); } +.uui-quoteblock, .uui-text blockquote { float: right; font-size: 15px; @@ -134,6 +153,7 @@ line-height: inherit; } +.uui-quoteblock:before, .uui-text blockquote:before { content: open-quote; margin-left: -0.4em; @@ -143,6 +163,7 @@ font-size: 2em; } +.uui-quoteblock:after, .uui-text blockquote:after { content: close-quote; margin-left: 0.04em; @@ -154,6 +175,7 @@ display: inline-block; } +.uui-ul, .uui-text ul { list-style-type: square; padding-left: var(--uui-size-layout-1); @@ -161,6 +183,7 @@ margin-bottom: var(--uui-size-layout-1); } +.uui-ol, .uui-text ol { padding-left: var(--uui-size-layout-1); margin-top: var(--uui-size-layout-1); diff --git a/packages/uui-input/lib/uui-input.element.ts b/packages/uui-input/lib/uui-input.element.ts index 72470bdf0..7a7d808b9 100644 --- a/packages/uui-input/lib/uui-input.element.ts +++ b/packages/uui-input/lib/uui-input.element.ts @@ -212,7 +212,7 @@ export class UUIInputElement extends UUIFormControlMixin( this.addEventListener('blur', () => { this.style.setProperty('--uui-show-focus-outline', ''); }); - this.addEventListener('keypress', this._onKeypress); + this.addEventListener('keydown', this.#onKeyDown); this.addValidator( 'tooShort', @@ -230,7 +230,7 @@ export class UUIInputElement extends UUIFormControlMixin( }); } - private _onKeypress(e: KeyboardEvent): void { + #onKeyDown(e: KeyboardEvent): void { if (this.type !== 'color' && e.key == 'Enter') { this.submit(); } diff --git a/packages/uui-input/lib/uui-input.test.ts b/packages/uui-input/lib/uui-input.test.ts index adbf05b44..8118f5f08 100644 --- a/packages/uui-input/lib/uui-input.test.ts +++ b/packages/uui-input/lib/uui-input.test.ts @@ -99,7 +99,7 @@ describe('UuiInputElement', () => { describe('events', () => { describe('focus', () => { it('emits a focus event when focused', async () => { - const listener = oneEvent(element, 'focus', false); + const listener = oneEvent(element, 'focus'); element.focus(); const event = await listener; expect(event).to.exist; @@ -108,7 +108,7 @@ describe('UuiInputElement', () => { }); describe('change', () => { it('emits a change event when native input fires one', async () => { - const listener = oneEvent(element, UUIInputEvent.CHANGE, false); + const listener = oneEvent(element, UUIInputEvent.CHANGE); input.dispatchEvent(new Event('change')); @@ -133,11 +133,7 @@ describe('UuiInputElement', () => { 'input', ) as HTMLInputElement; - const innerListener = oneEvent( - innerElement!, - UUIInputEvent.CHANGE, - false, - ); + const innerListener = oneEvent(innerElement!, UUIInputEvent.CHANGE); outerElement!.addEventListener(UUIInputEvent.CHANGE, () => { outerEventTriggered = true; }); @@ -208,8 +204,8 @@ describe('UuiInput in Form', () => { describe('submit', () => { it('should submit when pressing enter', async () => { - const listener = oneEvent(formElement, 'submit', false); - element.dispatchEvent(new KeyboardEvent('keypress', { key: 'Enter' })); + const listener = oneEvent(formElement, 'submit'); + element.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); const event = await listener; expect(event).to.exist; @@ -223,11 +219,11 @@ describe('UuiInput in Form', () => { let isFulfilled = false; - const listener = oneEvent(formElement, 'submit', false); + const listener = oneEvent(formElement, 'submit'); listener.then(() => (isFulfilled = true)); - element.dispatchEvent(new KeyboardEvent('keypress', { key: 'Enter' })); + element.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); await sleep(100); diff --git a/packages/uui-range-slider/lib/uui-range-slider.element.ts b/packages/uui-range-slider/lib/uui-range-slider.element.ts index 19702e2d0..8ee74a6a8 100644 --- a/packages/uui-range-slider/lib/uui-range-slider.element.ts +++ b/packages/uui-range-slider/lib/uui-range-slider.element.ts @@ -16,6 +16,11 @@ const TRACK_HEIGHT = 3; const TRACK_PADDING = 12; const STEP_MIN_WIDTH = 24; +const CountDecimalPlaces = (num: number) => { + const decimalIndex = num.toString().indexOf('.'); + return decimalIndex >= 0 ? num.toString().length - decimalIndex - 1 : 0; +}; + // TODO: ability to focus on the range, to enable keyboard interaction to move the range. // TODO: Ability to click outside a range, to move the range if the maxGap has been reached. // TODO: . @@ -299,7 +304,7 @@ export class UUIRangeSliderElement extends UUIFormControlMixin(LitElement, '') { constructor() { super(); // Keyboard - this.addEventListener('keypress', this._onKeypress); + this.addEventListener('keydown', this.#onKeyDown); // Mouse this.addEventListener('mousedown', this._onMouseDown); // Touch @@ -447,7 +452,7 @@ export class UUIRangeSliderElement extends UUIFormControlMixin(LitElement, '') { /** Events */ - private _onKeypress = (e: KeyboardEvent) => { + #onKeyDown = (e: KeyboardEvent) => { if (e.key == 'Enter') { this.submit(); } @@ -624,8 +629,16 @@ export class UUIRangeSliderElement extends UUIFormControlMixin(LitElement, '') { private _renderThumbValues() { return html`
- ${this._lowInputValue} - ${this._highInputValue} + ${this._lowInputValue.toFixed(CountDecimalPlaces(this._step))} + ${this._highInputValue.toFixed(CountDecimalPlaces(this._step))}
`; } @@ -656,7 +669,9 @@ export class UUIRangeSliderElement extends UUIFormControlMixin(LitElement, '') { let index = 0; const stepValues = new Array(stepAmount + 1) .fill(this._step) - .map(step => this._min + step * index++); + .map(step => + (this._min + step * index++).toFixed(CountDecimalPlaces(this._step)), + ); return html`
${stepValues.map(value => html`${value}`)} diff --git a/packages/uui-range-slider/lib/uui-range-slider.test.ts b/packages/uui-range-slider/lib/uui-range-slider.test.ts index c4a6cf2cc..d9185e0a7 100644 --- a/packages/uui-range-slider/lib/uui-range-slider.test.ts +++ b/packages/uui-range-slider/lib/uui-range-slider.test.ts @@ -78,7 +78,7 @@ describe('UUIRangeSliderElement', () => { describe('events', () => { describe('change', () => { it('emits a change event from inputLow when native input fires one', async () => { - const listener = oneEvent(element, UUIRangeSliderEvent.CHANGE, false); + const listener = oneEvent(element, UUIRangeSliderEvent.CHANGE); inputLow.dispatchEvent(new Event('change')); const event = await listener; expect(event).to.exist; @@ -86,7 +86,7 @@ describe('UUIRangeSliderElement', () => { expect(event!.target).to.equal(element); }); it('emits a change event from inputHigh when native input fires one', async () => { - const listener = oneEvent(element, UUIRangeSliderEvent.CHANGE, false); + const listener = oneEvent(element, UUIRangeSliderEvent.CHANGE); inputHigh.dispatchEvent(new Event('change')); const event = await listener; expect(event).to.exist; @@ -96,7 +96,7 @@ describe('UUIRangeSliderElement', () => { }); describe('input', () => { it('emits an input event from inputLow when native input fires one', async () => { - const listener = oneEvent(element, UUIRangeSliderEvent.INPUT, false); + const listener = oneEvent(element, UUIRangeSliderEvent.INPUT); inputLow.dispatchEvent(new Event('input')); const event = await listener; expect(event).to.exist; @@ -104,7 +104,7 @@ describe('UUIRangeSliderElement', () => { expect(event!.target).to.equal(element); }); it('emits an input event from inputHigh when native input fires one', async () => { - const listener = oneEvent(element, UUIRangeSliderEvent.INPUT, false); + const listener = oneEvent(element, UUIRangeSliderEvent.INPUT); inputHigh.dispatchEvent(new Event('input')); const event = await listener; expect(event).to.exist; @@ -164,8 +164,8 @@ describe('UUIRangeSlider in a form', () => { describe('submit', () => { it('should submit when pressing enter', async () => { - const listener = oneEvent(formElement, 'submit', false); - element.dispatchEvent(new KeyboardEvent('keypress', { key: 'Enter' })); + const listener = oneEvent(formElement, 'submit'); + element.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); const event = await listener; expect(event).to.exist; diff --git a/packages/uui-ref-node/lib/uui-ref-node.element.ts b/packages/uui-ref-node/lib/uui-ref-node.element.ts index 2ae97da50..f8e12b8f5 100644 --- a/packages/uui-ref-node/lib/uui-ref-node.element.ts +++ b/packages/uui-ref-node/lib/uui-ref-node.element.ts @@ -67,8 +67,18 @@ export class UUIRefNodeElement extends UUIRefElement { @state() private _iconSlotHasContent = false; - protected fallbackIcon = - ''; + protected fallbackIcon = ` + + + `; connectedCallback() { super.connectedCallback(); @@ -141,17 +151,17 @@ export class UUIRefNodeElement extends UUIRefElement { public render() { return html` - ${this.#renderSomething()} + ${this.#renderOpenPart()}
- + `; } - #renderSomething() { + #renderOpenPart() { if (this.readonly) { return html`${this.#renderContent()}`; } else { @@ -164,7 +174,7 @@ export class UUIRefNodeElement extends UUIRefElement { css` :host { min-width: 250px; - padding: calc(var(--uui-size-2) + 1px); + padding: 1px; } #content { @@ -179,6 +189,10 @@ export class UUIRefNodeElement extends UUIRefElement { color: inherit; text-decoration: none; cursor: pointer; + align-self: stretch; + display: flex; + flex-grow: 1; + padding: calc(var(--uui-size-2)); } #icon { @@ -204,11 +218,16 @@ export class UUIRefNodeElement extends UUIRefElement { font-size: var(--uui-type-small-size); } + :host([selectable]) #open-part { + flex-grow: 0; + padding: 0; + margin: calc(var(--uui-size-2)); + } + :host(:not([disabled])) #open-part:hover #icon { color: var(--uui-color-interactive-emphasis); } :host(:not([disabled])) #open-part:hover #name { - font-weight: 700; text-decoration: underline; color: var(--uui-color-interactive-emphasis); } diff --git a/packages/uui-ref/lib/uui-ref.element.ts b/packages/uui-ref/lib/uui-ref.element.ts index 63fada255..3b4782ce1 100644 --- a/packages/uui-ref/lib/uui-ref.element.ts +++ b/packages/uui-ref/lib/uui-ref.element.ts @@ -77,9 +77,11 @@ export class UUIRefElement extends SelectOnlyMixin( transition: --uui-card-before-opacity 120ms; } - :host(:focus) { - /** TODO: implement focus outline. */ - outline-color: #6ab4f0; + :host([selectable]:focus-visible) { + outline-color: var(--uui-color-focus); + outline-width: var(--uui-card-border-width); + outline-style: solid; + outline-offset: var(--uui-card-border-width); } :host([error]) { @@ -179,6 +181,19 @@ export class UUIRefElement extends SelectOnlyMixin( text-align: left; color: var(--uui-color-text); } + a { + text-decoration: none; + color: inherit; + } + + button:focus, + a:focus { + outline-color: var(--uui-color-focus); + outline-width: var(--uui-card-border-width); + outline-style: solid; + outline-offset: var(--uui-card-border-width); + border-radius: var(--uui-border-radius); + } slot[name='actions'] { display: flex; @@ -186,11 +201,16 @@ export class UUIRefElement extends SelectOnlyMixin( --uui-button-height: calc(var(--uui-size-2) * 4); margin-right: var(--uui-size-2); } + #tag-container { + margin: calc(var(--uui-size-2)); + } #actions-container { + margin: calc(var(--uui-size-2)); opacity: 0; transition: opacity 120ms; } :host(:hover) #actions-container, + :host(:focus) #actions-container, :host(:focus-within) #actions-container { opacity: 1; } @@ -208,8 +228,6 @@ export class UUIRefElement extends SelectOnlyMixin( } slot[name='tag'] { - flex-grow: 1; - display: flex; justify-content: flex-end; align-items: center; diff --git a/packages/uui-slider/lib/uui-slider.element.ts b/packages/uui-slider/lib/uui-slider.element.ts index dd1f750a9..612c22ec7 100644 --- a/packages/uui-slider/lib/uui-slider.element.ts +++ b/packages/uui-slider/lib/uui-slider.element.ts @@ -11,42 +11,14 @@ import { UUISliderEvent } from './UUISliderEvent'; const TRACK_PADDING = 12; const STEP_MIN_WIDTH = 24; -const RenderTrackSteps = (steps: number[], stepWidth: number) => { - return svg` - ${steps.map(el => { - if (stepWidth >= STEP_MIN_WIDTH) { - const x = Math.round(TRACK_PADDING + stepWidth * steps.indexOf(el)); - return svg``; - } - return svg``; - })} -`; -}; - -const RenderStepValues = ( - steps: number[], - stepWidth: number, - hide: boolean, -) => { - if (hide) return nothing; - - return html`
- ${steps.map( - el => - html` - ${steps.length <= 20 && stepWidth >= STEP_MIN_WIDTH - ? el.toFixed(0) - : nothing} - `, - )} -
`; -}; - const GenerateStepArray = (start: number, stop: number, step: number) => Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + i * step); +const CountDecimalPlaces = (num: number) => { + const decimalIndex = num.toString().indexOf('.'); + return decimalIndex >= 0 ? num.toString().length - decimalIndex - 1 : 0; +}; + /** * @element uui-slider * @description - Native `` wrapper. @@ -188,7 +160,7 @@ export class UUISliderElement extends UUIFormControlMixin(LitElement, '') { this.addEventListener('blur', () => { this.style.setProperty('--uui-show-focus-outline', ''); }); - this.addEventListener('keypress', this._onKeypress); + this.addEventListener('keydown', this.#onKeyDown); } /** @@ -264,7 +236,7 @@ export class UUISliderElement extends UUIFormControlMixin(LitElement, '') { this._stepWidth = this._calculateStepWidth(); }; - private _onKeypress(e: KeyboardEvent): void { + #onKeyDown(e: KeyboardEvent): void { if (e.key == 'Enter') { this.submit(); } @@ -295,6 +267,37 @@ export class UUISliderElement extends UUIFormControlMixin(LitElement, '') { this.dispatchEvent(new UUISliderEvent(UUISliderEvent.CHANGE)); } + renderTrackSteps() { + return svg` + ${this._steps.map(el => { + if (this._stepWidth >= STEP_MIN_WIDTH) { + const x = Math.round( + TRACK_PADDING + this._stepWidth * this._steps.indexOf(el), + ); + return svg``; + } + return svg``; + })} +`; + } + + renderStepValues() { + if (this.hideStepValues) return nothing; + + return html`
+ ${this._steps.map( + el => + html` + ${this._steps.length <= 20 && this._stepWidth >= STEP_MIN_WIDTH + ? el.toFixed(CountDecimalPlaces(this.step)) + : nothing} + `, + )} +
`; + } + render() { return html` - ${RenderTrackSteps(this._steps, this._stepWidth)} + ${this.renderTrackSteps()}
- ${RenderStepValues(this._steps, this._stepWidth, this.hideStepValues)} + ${this.renderStepValues()} `; } diff --git a/packages/uui-slider/lib/uui-slider.story.ts b/packages/uui-slider/lib/uui-slider.story.ts index af730a803..95288bb2f 100644 --- a/packages/uui-slider/lib/uui-slider.story.ts +++ b/packages/uui-slider/lib/uui-slider.story.ts @@ -56,3 +56,12 @@ export const Readonly: Story = { readonly: true, }, }; + +export const DecimalValue: Story = { + args: { + min: 0, + max: 1, + step: 0.1, + value: 0.5, + }, +}; diff --git a/packages/uui-slider/lib/uui-slider.test.ts b/packages/uui-slider/lib/uui-slider.test.ts index 5fd59efa2..cf52c58bc 100644 --- a/packages/uui-slider/lib/uui-slider.test.ts +++ b/packages/uui-slider/lib/uui-slider.test.ts @@ -73,7 +73,7 @@ describe('UuiSlider', () => { describe('events', () => { describe('change', () => { it('emits a change event when native input fires one', async () => { - const listener = oneEvent(element, UUISliderEvent.CHANGE, false); + const listener = oneEvent(element, UUISliderEvent.CHANGE); input.dispatchEvent(new Event('change')); @@ -85,7 +85,7 @@ describe('UuiSlider', () => { }); describe('input', () => { it('emits a input event when native input fires one', async () => { - const listener = oneEvent(element, UUISliderEvent.INPUT, false); + const listener = oneEvent(element, UUISliderEvent.INPUT); input.dispatchEvent(new Event('input')); @@ -150,8 +150,8 @@ describe('UuiSlider in Form', () => { describe('submit', () => { it('should submit when pressing enter', async () => { - const listener = oneEvent(formElement, 'submit', false); - element.dispatchEvent(new KeyboardEvent('keypress', { key: 'Enter' })); + const listener = oneEvent(formElement, 'submit'); + element.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); const event = await listener; expect(event).to.exist; diff --git a/packages/uui-symbol-file/lib/uui-symbol-file.element.ts b/packages/uui-symbol-file/lib/uui-symbol-file.element.ts index c949b300d..9db4512c1 100644 --- a/packages/uui-symbol-file/lib/uui-symbol-file.element.ts +++ b/packages/uui-symbol-file/lib/uui-symbol-file.element.ts @@ -1,6 +1,7 @@ import { LitElement, html, css } from 'lit'; import { defineElement } from '@umbraco-ui/uui-base/lib/registration'; import { property } from 'lit/decorators.js'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; /** * @element uui-file-symbol @@ -16,20 +17,30 @@ export class UUISymbolFileElement extends LitElement { type = ''; render() { - return html` - + + + ${this.type - ? html`${this.type.toUpperCase()}` - : ''} `; + ? html`${this.type.toUpperCase()}` + : ''} + `; } static styles = [ + UUITextStyles, css` :host { position: relative; @@ -38,8 +49,8 @@ export class UUISymbolFileElement extends LitElement { #file-type { position: absolute; - bottom: 24%; - left: 25.5%; + bottom: 20%; + left: 12%; margin-left: calc(var(--uui-size-3) * -1); padding: 0px var(--uui-size-3); font-weight: 700; @@ -48,10 +59,12 @@ export class UUISymbolFileElement extends LitElement { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + border-radius: var(--uui-border-radius); } #icon { - fill: var(--uui-color-border-standalone); + width: 100%; + color: var(--uui-color-border-standalone); } `, ]; diff --git a/packages/uui-symbol-folder/lib/uui-symbol-folder.element.ts b/packages/uui-symbol-folder/lib/uui-symbol-folder.element.ts index c6b371b63..cd10cc8f2 100644 --- a/packages/uui-symbol-folder/lib/uui-symbol-folder.element.ts +++ b/packages/uui-symbol-folder/lib/uui-symbol-folder.element.ts @@ -10,12 +10,16 @@ export class UUISymbolFolderElement extends LitElement { render() { return html` - `; + d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" /> + `; } static styles = [ @@ -28,7 +32,8 @@ export class UUISymbolFolderElement extends LitElement { } #icon { - fill: var(--uui-color-border); + width: 100%; + color: var(--uui-color-border-standalone); } `, ];