@api-client/ui 0.5.37 → 0.5.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/src/md/list/internals/ListItem.d.ts +24 -15
- package/build/src/md/list/internals/ListItem.d.ts.map +1 -1
- package/build/src/md/list/internals/ListItem.js +85 -59
- package/build/src/md/list/internals/ListItem.js.map +1 -1
- package/build/src/md/list/internals/ListItem.styles.js +11 -11
- package/build/src/md/list/internals/ListItem.styles.js.map +1 -1
- package/build/src/md/select/internals/Option.d.ts +4 -14
- package/build/src/md/select/internals/Option.d.ts.map +1 -1
- package/build/src/md/select/internals/Option.js +13 -28
- package/build/src/md/select/internals/Option.js.map +1 -1
- package/build/src/md/select/internals/Select.d.ts +1 -1
- package/build/src/md/select/internals/Select.d.ts.map +1 -1
- package/build/src/md/select/internals/Select.js +16 -5
- package/build/src/md/select/internals/Select.js.map +1 -1
- package/demo/md/select/index.ts +14 -4
- package/package.json +1 -1
- package/src/md/list/internals/ListItem.styles.ts +11 -11
- package/src/md/list/internals/ListItem.ts +68 -43
- package/src/md/select/internals/Option.ts +14 -26
- package/src/md/select/internals/Select.ts +16 -5
- package/test/md/select/Select.test.ts +29 -0
|
@@ -13,6 +13,7 @@ export enum ListItemLines {
|
|
|
13
13
|
one = 'one',
|
|
14
14
|
two = 'two',
|
|
15
15
|
three = 'three',
|
|
16
|
+
auto = 'auto',
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export enum ListItemImage {
|
|
@@ -24,28 +25,42 @@ export enum ListItemImage {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
|
-
* @slot
|
|
28
|
-
* @slot overline
|
|
29
|
-
* @slot start
|
|
30
|
-
* @slot end
|
|
31
|
-
* @slot end-text
|
|
32
|
-
* @slot supporting-text
|
|
28
|
+
* @slot - The main content of the list item, typically a label or title.
|
|
29
|
+
* @slot overline - The text displayed above the main content.
|
|
30
|
+
* @slot start - The content displayed at the start of the list item.
|
|
31
|
+
* @slot end - The content displayed at the end of the list item.
|
|
32
|
+
* @slot end-text - The text displayed at the end of the list item.
|
|
33
|
+
* @slot supporting-text - The supporting text displayed below the main content.
|
|
33
34
|
*/
|
|
34
35
|
export default class UiListItem extends UiElement {
|
|
35
36
|
@query('ui-ripple') accessor ripple!: UiRipple
|
|
36
37
|
|
|
38
|
+
#lines: ListItemLines = ListItemLines.one
|
|
39
|
+
|
|
40
|
+
get lines(): ListItemLines {
|
|
41
|
+
if (this.#lines === ListItemLines.auto) {
|
|
42
|
+
return this.hasOverline || this.hasSupportingText ? ListItemLines.two : ListItemLines.one
|
|
43
|
+
}
|
|
44
|
+
return this.#lines
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
/**
|
|
38
|
-
*
|
|
48
|
+
* The number of lines to render the list template for.
|
|
49
|
+
* @default ListItemLines.one
|
|
39
50
|
* @attribute
|
|
40
51
|
*/
|
|
41
|
-
@property({ type:
|
|
52
|
+
@property({ type: String, reflect: true })
|
|
53
|
+
set lines(value: ListItemLines) {
|
|
54
|
+
const oldValue = this.#lines
|
|
55
|
+
this.#lines = value
|
|
56
|
+
this.requestUpdate('lines', oldValue)
|
|
57
|
+
}
|
|
42
58
|
|
|
43
59
|
/**
|
|
44
|
-
* The
|
|
45
|
-
* @default ListItemLines.one
|
|
60
|
+
* Whether the chip is disabled. The user can't interact with the chip when `true`.
|
|
46
61
|
* @attribute
|
|
47
62
|
*/
|
|
48
|
-
@property({ type:
|
|
63
|
+
@property({ type: Boolean, reflect: true }) accessor disabled = false
|
|
49
64
|
|
|
50
65
|
/**
|
|
51
66
|
* The type of the list image.
|
|
@@ -67,12 +82,14 @@ export default class UiListItem extends UiElement {
|
|
|
67
82
|
@state() accessor hasStartItem = false
|
|
68
83
|
@state() accessor hasEndItem = false
|
|
69
84
|
@state() accessor hasEndTextItem = false
|
|
85
|
+
@state() accessor hasOverline = false
|
|
86
|
+
@state() accessor hasSupportingText = false
|
|
70
87
|
|
|
71
88
|
constructor() {
|
|
72
89
|
super()
|
|
73
90
|
|
|
74
|
-
this.lines = ListItemLines.one
|
|
75
91
|
this.image = ListItemImage.icon
|
|
92
|
+
this.lines = ListItemLines.one
|
|
76
93
|
|
|
77
94
|
this.addEventListener('click', this.handleClick.bind(this))
|
|
78
95
|
this.addEventListener('pointerdown', this.handlePointerDown.bind(this))
|
|
@@ -195,49 +212,57 @@ export default class UiListItem extends UiElement {
|
|
|
195
212
|
this.removeAttribute('tabindex')
|
|
196
213
|
}
|
|
197
214
|
|
|
198
|
-
|
|
199
|
-
const slot =
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
215
|
+
private _handleSlotChange(event: Event): boolean {
|
|
216
|
+
const slot = event.target as HTMLSlotElement
|
|
217
|
+
return slot.assignedNodes({ flatten: true }).length > 0
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
protected handleOverlineSlotChange(event: Event): void {
|
|
221
|
+
this.hasOverline = this._handleSlotChange(event)
|
|
205
222
|
this.requestUpdate()
|
|
206
223
|
}
|
|
207
224
|
|
|
208
|
-
protected
|
|
209
|
-
|
|
210
|
-
if (slot) {
|
|
211
|
-
this.hasEndItem = slot.assignedNodes({ flatten: true }).length > 0
|
|
212
|
-
} else {
|
|
213
|
-
this.hasEndItem = false
|
|
214
|
-
}
|
|
225
|
+
protected handleSupportingTextSlotChange(event: Event): void {
|
|
226
|
+
this.hasSupportingText = this._handleSlotChange(event)
|
|
215
227
|
this.requestUpdate()
|
|
216
228
|
}
|
|
217
229
|
|
|
218
|
-
protected
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
230
|
+
protected handleStartSlotChange(event: Event): void {
|
|
231
|
+
this.hasStartItem = this._handleSlotChange(event)
|
|
232
|
+
this.requestUpdate()
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
protected handleEndSlotChange(event: Event): void {
|
|
236
|
+
this.hasEndItem = this._handleSlotChange(event)
|
|
225
237
|
this.requestUpdate()
|
|
226
238
|
}
|
|
227
239
|
|
|
240
|
+
protected handleEndTextSlotChange(event: Event): void {
|
|
241
|
+
this.hasEndTextItem = this._handleSlotChange(event)
|
|
242
|
+
this.requestUpdate()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
getSurfaceClasses(): ClassInfo {
|
|
246
|
+
const result: ClassInfo = {
|
|
247
|
+
'surface': true,
|
|
248
|
+
'one-line': this.lines === ListItemLines.one,
|
|
249
|
+
'two-lines': this.lines === ListItemLines.two,
|
|
250
|
+
'three-lines': this.lines === ListItemLines.three,
|
|
251
|
+
}
|
|
252
|
+
return result
|
|
253
|
+
}
|
|
254
|
+
|
|
228
255
|
protected override render(): TemplateResult {
|
|
229
|
-
const surfaceClasses = classMap({
|
|
230
|
-
surface: true,
|
|
231
|
-
})
|
|
232
256
|
return html`
|
|
233
257
|
${this.renderFocusRing()} ${this.renderRipple()}
|
|
234
|
-
<div class="${
|
|
258
|
+
<div class="${classMap(this.getSurfaceClasses())}">
|
|
259
|
+
${this.renderStart()} ${this.renderBody()} ${this.renderEnd()}
|
|
260
|
+
</div>
|
|
235
261
|
`
|
|
236
262
|
}
|
|
237
263
|
|
|
238
264
|
protected renderRipple = (): TemplateResult => {
|
|
239
|
-
|
|
240
|
-
return html`<ui-ripple class="ripple" ?disabled="${disabled || isStatic}"></ui-ripple>`
|
|
265
|
+
return html`<ui-ripple class="ripple" ?disabled="${this.disabled || this.static}"></ui-ripple>`
|
|
241
266
|
}
|
|
242
267
|
|
|
243
268
|
protected renderFocusRing(): TemplateResult {
|
|
@@ -278,13 +303,13 @@ export default class UiListItem extends UiElement {
|
|
|
278
303
|
}
|
|
279
304
|
|
|
280
305
|
protected renderBody(): TemplateResult {
|
|
281
|
-
const { lines = ListItemLines.one } = this
|
|
282
|
-
const hasSupportingText = lines !== ListItemLines.one
|
|
283
306
|
return html`
|
|
284
307
|
<div class="body">
|
|
285
|
-
<slot name="overline"></slot>
|
|
308
|
+
<slot name="overline" @slotchange=${this.handleOverlineSlotChange}></slot>
|
|
286
309
|
<span class="headline"><slot></slot></span>
|
|
287
|
-
|
|
310
|
+
<span class="supporting-text"
|
|
311
|
+
><slot name="supporting-text" @slotchange=${this.handleSupportingTextSlotChange}></slot
|
|
312
|
+
></span>
|
|
288
313
|
</div>
|
|
289
314
|
`
|
|
290
315
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { html, PropertyValues, TemplateResult } from 'lit'
|
|
2
2
|
import { property } from 'lit/decorators.js'
|
|
3
|
-
import { classMap } from 'lit/directives/class-map.js'
|
|
4
|
-
import UiListItem from '../../list/internals/ListItem.js'
|
|
3
|
+
import { ClassInfo, classMap } from 'lit/directives/class-map.js'
|
|
4
|
+
import UiListItem, { ListItemLines } from '../../list/internals/ListItem.js'
|
|
5
5
|
import { nanoid } from 'nanoid'
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -89,11 +89,14 @@ export default class UiOption extends UiListItem {
|
|
|
89
89
|
return content.join(' ').trim() || this.value || ''
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
constructor() {
|
|
93
|
+
super()
|
|
94
|
+
this.lines = ListItemLines.auto
|
|
95
|
+
}
|
|
96
|
+
|
|
92
97
|
/**
|
|
93
98
|
* Initializes the option when it's connected to the DOM. Sets up ARIA attributes
|
|
94
99
|
* and generates a unique ID if one isn't provided.
|
|
95
|
-
*
|
|
96
|
-
* @protected
|
|
97
100
|
*/
|
|
98
101
|
override connectedCallback(): void {
|
|
99
102
|
super.connectedCallback()
|
|
@@ -106,9 +109,6 @@ export default class UiOption extends UiListItem {
|
|
|
106
109
|
/**
|
|
107
110
|
* Handles property updates and triggers appropriate side effects.
|
|
108
111
|
* Currently monitors the `selected` property to update selection state.
|
|
109
|
-
*
|
|
110
|
-
* @param {PropertyValues<this>} changedProperties - Map of changed properties
|
|
111
|
-
* @protected
|
|
112
112
|
*/
|
|
113
113
|
protected override updated(changedProperties: PropertyValues<this>): void {
|
|
114
114
|
super.updated(changedProperties)
|
|
@@ -143,7 +143,7 @@ export default class UiOption extends UiListItem {
|
|
|
143
143
|
* Handles click events on the option. Prevents default behavior and dispatches
|
|
144
144
|
* a custom 'select' event that the parent select component can listen to.
|
|
145
145
|
*
|
|
146
|
-
* @param
|
|
146
|
+
* @param e - The click event
|
|
147
147
|
* @protected
|
|
148
148
|
* @fires select - Custom event with option details in event.detail
|
|
149
149
|
* @example
|
|
@@ -175,23 +175,11 @@ export default class UiOption extends UiListItem {
|
|
|
175
175
|
)
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
*
|
|
182
|
-
* @returns {TemplateResult} The rendered template
|
|
183
|
-
* @protected
|
|
184
|
-
*/
|
|
185
|
-
protected override render(): TemplateResult {
|
|
186
|
-
const surfaceClasses = classMap({
|
|
187
|
-
surface: true,
|
|
178
|
+
override getSurfaceClasses(): ClassInfo {
|
|
179
|
+
return {
|
|
180
|
+
...super.getSurfaceClasses(),
|
|
188
181
|
selected: this.selected,
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return html`
|
|
192
|
-
${this.renderFocusRing()} ${this.renderRipple()}
|
|
193
|
-
<div class="${surfaceClasses}">${this.renderStart()} ${this.renderBody()} ${this.renderEnd()}</div>
|
|
194
|
-
`
|
|
182
|
+
}
|
|
195
183
|
}
|
|
196
184
|
|
|
197
185
|
/**
|
|
@@ -200,10 +188,10 @@ export default class UiOption extends UiListItem {
|
|
|
200
188
|
*/
|
|
201
189
|
protected override renderEnd(): TemplateResult {
|
|
202
190
|
return html`
|
|
203
|
-
<div class="
|
|
191
|
+
<div class="${classMap(this.getEndClasses())}">
|
|
204
192
|
${this.selected ? html`<ui-icon icon="check" class="selection-icon"></ui-icon>` : ''}
|
|
205
193
|
<slot name="end" @slotchange=${this.handleEndSlotChange}></slot>
|
|
206
|
-
<slot name="end-text" class="trailing-supporting-text"></slot>
|
|
194
|
+
<slot name="end-text" class="trailing-supporting-text" @slotchange=${this.handleEndTextSlotChange}></slot>
|
|
207
195
|
</div>
|
|
208
196
|
`
|
|
209
197
|
}
|
|
@@ -354,12 +354,17 @@ export default class UiSelect extends LitElement {
|
|
|
354
354
|
|
|
355
355
|
protected override firstUpdated(cp: PropertyValues): void {
|
|
356
356
|
super.firstUpdated(cp)
|
|
357
|
-
this.
|
|
358
|
-
|
|
357
|
+
this.updateComplete.then(() => {
|
|
358
|
+
this.setCurrentOption()
|
|
359
|
+
this.#internals.setFormValue(this.value ?? null)
|
|
360
|
+
// We need to update here as event with `value` and `selectedOption`
|
|
361
|
+
// already set, the `renderValue` might have incorrect value
|
|
362
|
+
// due to the DOM update.
|
|
363
|
+
this.requestUpdate()
|
|
364
|
+
})
|
|
359
365
|
}
|
|
360
366
|
|
|
361
|
-
protected
|
|
362
|
-
await this.updateComplete
|
|
367
|
+
protected setCurrentOption(): void {
|
|
363
368
|
const options = this.querySelectorAll<UiOption>('ui-option')
|
|
364
369
|
if (this.value) {
|
|
365
370
|
this.selectedOption = Array.from(options).find((option) => option.value === this.value) || null
|
|
@@ -459,6 +464,11 @@ export default class UiSelect extends LitElement {
|
|
|
459
464
|
if (this.disabled || e.defaultPrevented) return
|
|
460
465
|
e.preventDefault()
|
|
461
466
|
e.stopPropagation()
|
|
467
|
+
if (this.open && e.target === this) {
|
|
468
|
+
// If the select is already open and clicked again, close it
|
|
469
|
+
this.open = false
|
|
470
|
+
return
|
|
471
|
+
}
|
|
462
472
|
this.open = true
|
|
463
473
|
}
|
|
464
474
|
|
|
@@ -490,6 +500,7 @@ export default class UiSelect extends LitElement {
|
|
|
490
500
|
const item = e.detail.item
|
|
491
501
|
this.selectedOption = item
|
|
492
502
|
this.#value = item.value
|
|
503
|
+
this.#internals.setFormValue(this.value ?? null)
|
|
493
504
|
this.open = false
|
|
494
505
|
|
|
495
506
|
// Dispatch change event
|
|
@@ -559,7 +570,7 @@ export default class UiSelect extends LitElement {
|
|
|
559
570
|
// When options change, re-evaluate the current selection
|
|
560
571
|
// only if we don't have an explicit value set
|
|
561
572
|
if (!this.value) {
|
|
562
|
-
|
|
573
|
+
this.setCurrentOption()
|
|
563
574
|
this.requestUpdate()
|
|
564
575
|
}
|
|
565
576
|
}
|
|
@@ -274,6 +274,35 @@ describe('md', () => {
|
|
|
274
274
|
assert.equal(formData.get('fruit'), 'apple')
|
|
275
275
|
})
|
|
276
276
|
|
|
277
|
+
it('should update form value on selection change', async () => {
|
|
278
|
+
const form = await formFixture()
|
|
279
|
+
const select = form.querySelector('ui-select')!
|
|
280
|
+
|
|
281
|
+
// Simulate selection of 'banana'
|
|
282
|
+
const bananaOption = select.querySelector('ui-option[value="banana"]') as UiOption
|
|
283
|
+
select.handleSelect(new CustomEvent('select', { detail: { item: bananaOption } }))
|
|
284
|
+
await select.updateComplete
|
|
285
|
+
|
|
286
|
+
const formData = new FormData(form)
|
|
287
|
+
assert.equal(formData.get('fruit'), 'banana', 'Form data should be "banana" after selection')
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
it('should clear form value when value is set to undefined', async () => {
|
|
291
|
+
const form = await formFixture()
|
|
292
|
+
const select = form.querySelector('ui-select')!
|
|
293
|
+
select.value = 'apple'
|
|
294
|
+
await select.updateComplete
|
|
295
|
+
|
|
296
|
+
let formData = new FormData(form)
|
|
297
|
+
assert.equal(formData.get('fruit'), 'apple', 'Form data should be "apple" initially')
|
|
298
|
+
|
|
299
|
+
select.value = undefined
|
|
300
|
+
await select.updateComplete
|
|
301
|
+
|
|
302
|
+
formData = new FormData(form)
|
|
303
|
+
assert.isNull(formData.get('fruit'), 'Form data should be null after clearing value')
|
|
304
|
+
})
|
|
305
|
+
|
|
277
306
|
it('should not submit when no value selected', async () => {
|
|
278
307
|
const form = await formFixture()
|
|
279
308
|
const formData = new FormData(form)
|