Skip to content

Commit 67d056d

Browse files
authored
Merge branch 'v1/contrib' into feature/relative-time
2 parents 657265c + c305dfc commit 67d056d

File tree

30 files changed

+690
-291
lines changed

30 files changed

+690
-291
lines changed

packages/uui-base/lib/mixins/SelectableMixin.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ export const SelectableMixin = <T extends Constructor<LitElement>>(
5454
const oldVal = this._selectable;
5555
this._selectable = newVal;
5656
// Potentially problematic as a component might need focus for another feature when not selectable:
57-
if (!this.selectableTarget) {
58-
// If not selectable target, then make it self selectable. (A selectable target should be made focusable by the component itself)
57+
if (this.selectableTarget === this) {
58+
// If the selectable target, then make it self selectable. (A different selectable target should be made focusable by the component itself)
5959
this.setAttribute('tabindex', `${newVal ? '0' : '-1'}`);
6060
}
6161
this.requestUpdate('selectable', oldVal);
@@ -80,10 +80,16 @@ export const SelectableMixin = <T extends Constructor<LitElement>>(
8080
}
8181

8282
private handleSelectKeydown = (e: KeyboardEvent) => {
83-
//if (e.composedPath().indexOf(this.selectableTarget) !== -1) {
84-
if (this.selectableTarget === this) {
85-
if (e.key !== ' ' && e.key !== 'Enter') return;
86-
this._toggleSelect();
83+
const composePath = e.composedPath();
84+
if (
85+
(this._selectable || (this.deselectable && this.selected)) &&
86+
composePath.indexOf(this.selectableTarget) === 0
87+
) {
88+
if (this.selectableTarget === this) {
89+
if (e.code !== 'Space' && e.code !== 'Enter') return;
90+
this._toggleSelect();
91+
e.preventDefault();
92+
}
8793
}
8894
};
8995

@@ -112,7 +118,7 @@ export const SelectableMixin = <T extends Constructor<LitElement>>(
112118
}
113119

114120
private _toggleSelect() {
115-
// Only allow for select-interaction if selectable is true. Deselectable is ignorered in this case, we do not want a DX where only deselection is a possibility..
121+
// Only allow for select-interaction if selectable is true. Deselectable is ignored in this case, we do not want a DX where only deselection is a possibility..
116122
if (!this.selectable) return;
117123
if (this.deselectable === false) {
118124
this._select();

packages/uui-boolean-input/lib/uui-boolean-input.element.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,14 @@ export abstract class UUIBooleanInputElement extends UUIFormControlMixin(
114114
this._value = 'on';
115115
}
116116
this.inputRole = inputRole;
117-
this.addEventListener('keypress', this._onKeypress);
117+
this.addEventListener('keydown', this.#onKeyDown);
118118
}
119119

120120
protected getFormElement(): HTMLInputElement {
121121
return this._input;
122122
}
123123

124-
private _onKeypress(e: KeyboardEvent): void {
124+
#onKeyDown(e: KeyboardEvent): void {
125125
if (e.key == 'Enter') {
126126
this.submit();
127127
}

packages/uui-boolean-input/lib/uui-boolean-input.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,8 @@ describe('BooleanInputBaseElement in a Form', () => {
128128

129129
describe('submit', () => {
130130
it('should submit when pressing enter', async () => {
131-
const listener = oneEvent(formElement, 'submit', false);
132-
element.dispatchEvent(new KeyboardEvent('keypress', { key: 'Enter' }));
131+
const listener = oneEvent(formElement, 'submit');
132+
element.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }));
133133

134134
const event = await listener;
135135
expect(event).to.exist;

packages/uui-card-block-type/lib/uui-card-block-type.test.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ describe('UUICardBlockTypeElement', () => {
7979
describe('events', () => {
8080
describe('open', () => {
8181
it('emits a open event when open-part is clicked', async () => {
82-
const listener = oneEvent(element, UUICardEvent.OPEN, false);
82+
const listener = oneEvent(element, UUICardEvent.OPEN);
8383
const infoElement: HTMLElement | null =
8484
element.shadowRoot!.querySelector('#open-part');
8585
infoElement?.click();
@@ -93,25 +93,42 @@ describe('UUICardBlockTypeElement', () => {
9393
it('emits a selected event when selectable', async () => {
9494
element.selectable = true;
9595
await elementUpdated(element);
96-
const listener = oneEvent(element, UUISelectableEvent.SELECTED, false);
96+
const listener = oneEvent(element, UUISelectableEvent.SELECTED);
9797
element.click();
9898
const event = await listener;
9999
expect(event).to.exist;
100100
expect(event.type).to.equal(UUISelectableEvent.SELECTED);
101101
expect(element.selected).to.be.true;
102102
});
103+
104+
it('can be selected with keyboard', async () => {
105+
element.selectable = true;
106+
await elementUpdated(element);
107+
const listener = oneEvent(element, UUISelectableEvent.SELECTED);
108+
element.dispatchEvent(new KeyboardEvent('keydown', { code: 'Enter' }));
109+
const event = await listener;
110+
expect(event).to.exist;
111+
expect(event.type).to.equal(UUISelectableEvent.SELECTED);
112+
expect(element.selected).to.be.true;
113+
114+
const unselectedListener = oneEvent(
115+
element,
116+
UUISelectableEvent.DESELECTED,
117+
);
118+
element.dispatchEvent(new KeyboardEvent('keydown', { code: 'Space' }));
119+
const event2 = await unselectedListener;
120+
expect(event2).to.exist;
121+
expect(event2.type).to.equal(UUISelectableEvent.DESELECTED);
122+
expect(element.selected).to.be.false;
123+
});
103124
});
104125

105126
describe('deselect', () => {
106127
it('emits a deselected event when preselected', async () => {
107128
element.selectable = true;
108129
element.selected = true;
109130
await elementUpdated(element);
110-
const listener = oneEvent(
111-
element,
112-
UUISelectableEvent.DESELECTED,
113-
false,
114-
);
131+
const listener = oneEvent(element, UUISelectableEvent.DESELECTED);
115132
element.click();
116133
const event = await listener;
117134
expect(event).to.exist;

packages/uui-card-content-node/lib/uui-card-content-node.element.ts

Lines changed: 85 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,30 @@ export class UUICardContentNodeElement extends UUICardElement {
2424
@property({ type: String })
2525
name = '';
2626

27+
/**
28+
* Node details
29+
* @type {string}
30+
* @attr
31+
* @default ''
32+
*/
33+
@property({ type: String })
34+
detail = '';
35+
2736
@state()
2837
private _iconSlotHasContent = false;
2938

30-
protected fallbackIcon =
31-
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M396.441 138.878l-83.997-83.993-7.331-7.333H105.702v416.701h298.071V146.214l-7.332-7.336zM130.74 439.217V72.591h141.613c37.201 0 19.274 88.18 19.274 88.18s86-20.901 87.104 18.534v259.912H130.74z"></path></svg>';
39+
protected fallbackIcon = `<svg
40+
xmlns="http://www.w3.org/2000/svg"
41+
viewBox="0 0 24 24"
42+
fill="none"
43+
stroke="currentColor"
44+
stroke-width="1.75"
45+
stroke-linecap="round"
46+
stroke-linejoin="round"
47+
id="icon">
48+
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
49+
<path d="M14 2v4a2 2 0 0 0 2 2h4" />
50+
</svg>`;
3251

3352
private _onSlotIconChange(event: Event) {
3453
this._iconSlotHasContent =
@@ -41,18 +60,37 @@ export class UUICardContentNodeElement extends UUICardElement {
4160
return html`<uui-icon .svg="${this.fallbackIcon}"></uui-icon>`;
4261
}
4362

63+
protected renderDetail() {
64+
return html`<small id="detail"
65+
>${this.detail}<slot name="detail"></slot></small
66+
><slot id="default"></slot>`;
67+
}
68+
69+
#renderContent() {
70+
return html`
71+
<span id="content">
72+
<span id="item">
73+
<span id="icon">
74+
<slot name="icon" @slotchange=${this._onSlotIconChange}></slot>
75+
${this._iconSlotHasContent === false
76+
? this._renderFallbackIcon()
77+
: ''}
78+
</span>
79+
<div id="name">${this.name}<slot name="name"></slot></div>
80+
</span>
81+
${this.renderDetail()}
82+
</span>
83+
`;
84+
}
85+
4486
#renderButton() {
45-
return html`<div
87+
return html`<button
4688
id="open-part"
4789
tabindex=${this.disabled ? (nothing as any) : 0}
4890
@click=${this.handleOpenClick}
4991
@keydown=${this.handleOpenKeydown}>
50-
<span id="icon">
51-
<slot name="icon" @slotchange=${this._onSlotIconChange}></slot>
52-
${this._iconSlotHasContent === false ? this._renderFallbackIcon() : ''}
53-
</span>
54-
<span id="name"> ${this.name} </span>
55-
</div>`;
92+
${this.#renderContent()}
93+
</button>`;
5694
}
5795

5896
#renderLink() {
@@ -67,11 +105,7 @@ export class UUICardContentNodeElement extends UUICardElement {
67105
this.target === '_blank' ? 'noopener noreferrer' : undefined,
68106
),
69107
)}>
70-
<span id="icon">
71-
<slot name="icon" @slotchange=${this._onSlotIconChange}></slot>
72-
${this._iconSlotHasContent === false ? this._renderFallbackIcon() : ''}
73-
</span>
74-
<span id="name"> ${this.name} </span>
108+
${this.#renderContent()}
75109
</a>`;
76110
}
77111

@@ -81,7 +115,6 @@ export class UUICardContentNodeElement extends UUICardElement {
81115
<!-- Select border must be right after #open-part -->
82116
<div id="select-border"></div>
83117
84-
<slot></slot>
85118
<slot name="tag"></slot>
86119
<slot name="actions"></slot>
87120
`;
@@ -94,7 +127,6 @@ export class UUICardContentNodeElement extends UUICardElement {
94127
min-width: 250px;
95128
flex-direction: column;
96129
justify-content: space-between;
97-
padding: var(--uui-size-3) var(--uui-size-4);
98130
}
99131
100132
slot[name='tag'] {
@@ -132,30 +164,57 @@ export class UUICardContentNodeElement extends UUICardElement {
132164
line-height: calc(2 * var(--uui-size-3));
133165
}
134166
135-
#icon {
136-
font-size: 1.2em;
137-
margin-right: var(--uui-size-1);
138-
}
139-
140167
#open-part {
141168
display: flex;
142169
position: relative;
143170
font-weight: 700;
144171
align-items: center;
145172
cursor: pointer;
173+
flex-grow: 1;
174+
padding: var(--uui-size-space-4) var(--uui-size-space-5);
175+
}
176+
177+
#content {
178+
align-self: stretch;
179+
display: flex;
180+
flex-direction: column;
146181
}
147182
148-
:host([disabled]) #open-part {
183+
#item {
184+
position: relative;
185+
display: flex;
186+
align-self: stretch;
187+
line-height: normal;
188+
align-items: center;
189+
margin-top: var(--uui-size-1);
190+
}
191+
192+
#icon {
193+
font-size: 1.2em;
194+
margin-right: var(--uui-size-1);
195+
}
196+
197+
:host([selectable]) #open-part {
198+
padding: 0;
199+
margin: var(--uui-size-space-4) var(--uui-size-space-5);
200+
}
201+
202+
:host([disabled]) #name {
149203
pointer-events: none;
150204
}
151205
152-
#open-part:hover {
206+
:host(:not([disabled])) #open-part:hover #icon {
207+
color: var(--uui-color-interactive-emphasis);
208+
}
209+
:host(:not([disabled])) #open-part:hover #name {
153210
text-decoration: underline;
154211
color: var(--uui-color-interactive-emphasis);
155212
}
156-
157-
#name {
158-
margin-top: 4px;
213+
:host(:not([disabled])) #open-part:hover #detail {
214+
color: var(--uui-color-interactive-emphasis);
215+
}
216+
:host(:not([disabled])) #open-part:hover #default {
217+
color: var(--uui-color-interactive-emphasis);
159218
}
160219
`,
161220
];

packages/uui-card-content-node/lib/uui-card-content-node.test.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ describe('UUICardContentNodeElement', () => {
7575
describe('events', () => {
7676
describe('open', () => {
7777
it('emits a open event when info is clicked', async () => {
78-
const listener = oneEvent(element, UUICardEvent.OPEN, false);
78+
const listener = oneEvent(element, UUICardEvent.OPEN);
7979
const infoElement: HTMLElement | null =
8080
element.shadowRoot!.querySelector('#open-part');
8181
infoElement?.click();
@@ -85,7 +85,7 @@ describe('UUICardContentNodeElement', () => {
8585
});
8686

8787
it('emits a open event when icon is clicked', async () => {
88-
const listener = oneEvent(element, UUICardEvent.OPEN, false);
88+
const listener = oneEvent(element, UUICardEvent.OPEN);
8989
const iconElement: HTMLElement | null =
9090
element.shadowRoot!.querySelector('#icon');
9191
iconElement?.click();
@@ -99,25 +99,42 @@ describe('UUICardContentNodeElement', () => {
9999
it('emits a selected event when selectable', async () => {
100100
element.selectable = true;
101101
await elementUpdated(element);
102-
const listener = oneEvent(element, UUISelectableEvent.SELECTED, false);
102+
const listener = oneEvent(element, UUISelectableEvent.SELECTED);
103103
element.click();
104104
const event = await listener;
105105
expect(event).to.exist;
106106
expect(event.type).to.equal(UUISelectableEvent.SELECTED);
107107
expect(element.selected).to.be.true;
108108
});
109+
110+
it('can be selected with keyboard', async () => {
111+
element.selectable = true;
112+
await elementUpdated(element);
113+
const listener = oneEvent(element, UUISelectableEvent.SELECTED);
114+
element.dispatchEvent(new KeyboardEvent('keydown', { code: 'Enter' }));
115+
const event = await listener;
116+
expect(event).to.exist;
117+
expect(event.type).to.equal(UUISelectableEvent.SELECTED);
118+
expect(element.selected).to.be.true;
119+
120+
const unselectedListener = oneEvent(
121+
element,
122+
UUISelectableEvent.DESELECTED,
123+
);
124+
element.dispatchEvent(new KeyboardEvent('keydown', { code: 'Space' }));
125+
const event2 = await unselectedListener;
126+
expect(event2).to.exist;
127+
expect(event2.type).to.equal(UUISelectableEvent.DESELECTED);
128+
expect(element.selected).to.be.false;
129+
});
109130
});
110131

111132
describe('deselect', () => {
112133
it('emits a deselected event when preselected', async () => {
113134
element.selectable = true;
114135
element.selected = true;
115136
await elementUpdated(element);
116-
const listener = oneEvent(
117-
element,
118-
UUISelectableEvent.DESELECTED,
119-
false,
120-
);
137+
const listener = oneEvent(element, UUISelectableEvent.DESELECTED);
121138
element.click();
122139
const event = await listener;
123140
expect(event).to.exist;

0 commit comments

Comments
 (0)