@api-client/ui 0.5.3 → 0.5.4
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/dialog/internals/ConfirmDialog.d.ts +39 -0
- package/build/src/md/dialog/internals/ConfirmDialog.d.ts.map +1 -0
- package/build/src/md/dialog/internals/ConfirmDialog.js +75 -0
- package/build/src/md/dialog/internals/ConfirmDialog.js.map +1 -0
- package/build/src/md/dialog/internals/Dialog.d.ts +6 -0
- package/build/src/md/dialog/internals/Dialog.d.ts.map +1 -1
- package/build/src/md/dialog/internals/Dialog.js +43 -5
- package/build/src/md/dialog/internals/Dialog.js.map +1 -1
- package/build/src/md/dialog/internals/Dialog.styles.d.ts.map +1 -1
- package/build/src/md/dialog/internals/Dialog.styles.js +39 -1
- package/build/src/md/dialog/internals/Dialog.styles.js.map +1 -1
- package/build/src/md/dialog/ui-confirm-dialog.d.ts +48 -0
- package/build/src/md/dialog/ui-confirm-dialog.d.ts.map +1 -0
- package/build/src/md/dialog/ui-confirm-dialog.js +64 -0
- package/build/src/md/dialog/ui-confirm-dialog.js.map +1 -0
- package/demo/md/dialog/confirm-dialog.html +49 -0
- package/demo/md/dialog/confirm-dialog.ts +121 -0
- package/demo/md/dialog/dialog.ts +76 -1
- package/demo/md/index.html +2 -0
- package/package.json +1 -1
- package/src/md/dialog/README.md +212 -0
- package/src/md/dialog/internals/ConfirmDialog.ts +79 -0
- package/src/md/dialog/internals/Dialog.styles.ts +39 -1
- package/src/md/dialog/internals/Dialog.ts +17 -4
- package/src/md/dialog/ui-confirm-dialog.ts +52 -0
- package/test/README.md +2 -0
- package/test/md/dialog/UiConfirmDialog.test.ts +131 -0
- package/test/md/dialog/UiDialog.test.ts +42 -0
|
@@ -123,6 +123,13 @@ export default class UiDialog extends UiElement implements TypedEvents<DialogEve
|
|
|
123
123
|
*/
|
|
124
124
|
@property({ type: String }) accessor confirmLabel: string | undefined
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* When true, styles the confirm button with error colors to indicate
|
|
128
|
+
* a destructive action (e.g., delete, remove, etc.).
|
|
129
|
+
* @attribute
|
|
130
|
+
*/
|
|
131
|
+
@property({ type: Boolean }) accessor destructive = false
|
|
132
|
+
|
|
126
133
|
/**
|
|
127
134
|
* Part of the imperative access to the element.
|
|
128
135
|
* When set, it wraps the content in a `<form>` element.
|
|
@@ -300,6 +307,9 @@ export default class UiDialog extends UiElement implements TypedEvents<DialogEve
|
|
|
300
307
|
|
|
301
308
|
override handleKeyDown(e: KeyboardEvent): void {
|
|
302
309
|
super.handleKeyDown(e)
|
|
310
|
+
if (e.defaultPrevented) {
|
|
311
|
+
return
|
|
312
|
+
}
|
|
303
313
|
if (e.key === 'Escape') {
|
|
304
314
|
this.handleInteraction('dismiss')
|
|
305
315
|
}
|
|
@@ -389,9 +399,10 @@ export default class UiDialog extends UiElement implements TypedEvents<DialogEve
|
|
|
389
399
|
}
|
|
390
400
|
|
|
391
401
|
override render(): TemplateResult {
|
|
392
|
-
const { useForm } = this
|
|
402
|
+
const { useForm, modal } = this
|
|
403
|
+
const dialogClass = modal ? 'modal' : 'non-modal'
|
|
393
404
|
return html`
|
|
394
|
-
<dialog @close="${this.handleDialogClose}" part="dialog">
|
|
405
|
+
<dialog @close="${this.handleDialogClose}" part="dialog" class="${dialogClass}">
|
|
395
406
|
<div class="container">${useForm ? this.#renderFormContent() : this.renderContent()}</div>
|
|
396
407
|
</dialog>
|
|
397
408
|
`
|
|
@@ -413,6 +424,7 @@ export default class UiDialog extends UiElement implements TypedEvents<DialogEve
|
|
|
413
424
|
const classes: ClassInfo = {
|
|
414
425
|
'icon': true,
|
|
415
426
|
'with-icon': this.hasIcon,
|
|
427
|
+
'destructive': this.destructive,
|
|
416
428
|
}
|
|
417
429
|
return html`
|
|
418
430
|
<div class="${classMap(classes)}" part="icon">
|
|
@@ -473,17 +485,18 @@ export default class UiDialog extends UiElement implements TypedEvents<DialogEve
|
|
|
473
485
|
}
|
|
474
486
|
|
|
475
487
|
#renderConfirmButton(): TemplateResult | typeof nothing {
|
|
476
|
-
const { confirmLabel, confirmValue = 'confirm', useForm } = this
|
|
488
|
+
const { confirmLabel, confirmValue = 'confirm', useForm, destructive } = this
|
|
477
489
|
if (!confirmLabel) {
|
|
478
490
|
return nothing
|
|
479
491
|
}
|
|
480
492
|
const type = useForm ? 'submit' : 'button'
|
|
493
|
+
const buttonClass = destructive ? 'internal-button destructive' : 'internal-button'
|
|
481
494
|
return html`
|
|
482
495
|
<ui-button
|
|
483
496
|
color="text"
|
|
484
497
|
type="${type}"
|
|
485
498
|
value="${confirmValue}"
|
|
486
|
-
class="
|
|
499
|
+
class="${buttonClass}"
|
|
487
500
|
@click="${this.handleConfirm}"
|
|
488
501
|
part="positive-action"
|
|
489
502
|
>${confirmLabel}</ui-button
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CSSResultOrNative } from 'lit'
|
|
2
|
+
import { customElement } from 'lit/decorators.js'
|
|
3
|
+
import Element from './internals/ConfirmDialog.js'
|
|
4
|
+
import dialogStyles from './internals/Dialog.styles.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A simple Material Design 3 styled confirm dialog component.
|
|
8
|
+
*
|
|
9
|
+
* This dialog is designed specifically for confirmation workflows where users
|
|
10
|
+
* need to confirm or dismiss an action. It provides a clean, accessible interface
|
|
11
|
+
* with customizable button labels and content.
|
|
12
|
+
*
|
|
13
|
+
* **Features:**
|
|
14
|
+
* - Material Design 3 styling
|
|
15
|
+
* - Customizable confirm and dismiss button labels
|
|
16
|
+
* - Modal by default
|
|
17
|
+
* - Accessible keyboard navigation
|
|
18
|
+
* - Slot-based content structure
|
|
19
|
+
*
|
|
20
|
+
* **Usage:**
|
|
21
|
+
* ```html
|
|
22
|
+
* <ui-confirm-dialog .open="${showDialog}" @close="${handleClose}">
|
|
23
|
+
* <span slot="title">Confirm Action</span>
|
|
24
|
+
* <p>Are you sure you want to proceed with this action?</p>
|
|
25
|
+
* </ui-confirm-dialog>
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* **Customizing Button Labels:**
|
|
29
|
+
* ```html
|
|
30
|
+
* <ui-confirm-dialog
|
|
31
|
+
* confirmLabel="Delete"
|
|
32
|
+
* dismissLabel="Keep"
|
|
33
|
+
* .open="${showDialog}"
|
|
34
|
+
* >
|
|
35
|
+
* <span slot="title">Delete Item</span>
|
|
36
|
+
* <p>This action cannot be undone.</p>
|
|
37
|
+
* </ui-confirm-dialog>
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @slot title - The dialog title
|
|
41
|
+
* @slot - The main content body
|
|
42
|
+
*/
|
|
43
|
+
@customElement('ui-confirm-dialog')
|
|
44
|
+
export class UiConfirmDialogElement extends Element {
|
|
45
|
+
static override styles: CSSResultOrNative[] = [dialogStyles]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
declare global {
|
|
49
|
+
interface HTMLElementTagNameMap {
|
|
50
|
+
'ui-confirm-dialog': UiConfirmDialogElement
|
|
51
|
+
}
|
|
52
|
+
}
|
package/test/README.md
CHANGED
|
@@ -49,6 +49,8 @@ npm run test:watch
|
|
|
49
49
|
npm run test:coverage
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
> ⚠️ **Note**: This project uses Web Test Runner, not Jest. Test filtering options like `--testPathPattern` from Jest are not available. To run specific tests, you can temporarily modify the `files` pattern in `web-test-runner.config.js` or use pattern matching if supported by your Web Test Runner version. For most cases, running all tests is recommended as the test suite is optimized for speed.
|
|
53
|
+
|
|
52
54
|
## Global Test Utilities
|
|
53
55
|
|
|
54
56
|
The setup provides global utilities accessible via `window.testUtils`:
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { fixture, html, assert } from '@open-wc/testing'
|
|
2
|
+
import '../../../src/md/dialog/ui-confirm-dialog.js'
|
|
3
|
+
import type UiConfirmDialog from '../../../src/md/dialog/internals/ConfirmDialog.js'
|
|
4
|
+
|
|
5
|
+
describe('UiConfirmDialog', () => {
|
|
6
|
+
async function basicFixture(): Promise<UiConfirmDialog> {
|
|
7
|
+
return fixture(html`<ui-confirm-dialog></ui-confirm-dialog>`)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function customLabelsFixture(): Promise<UiConfirmDialog> {
|
|
11
|
+
return fixture(html`<ui-confirm-dialog confirmLabel="Delete" dismissLabel="Keep"></ui-confirm-dialog>`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function withContentFixture(): Promise<UiConfirmDialog> {
|
|
15
|
+
return fixture(html`
|
|
16
|
+
<ui-confirm-dialog>
|
|
17
|
+
<span slot="title">Confirm Action</span>
|
|
18
|
+
<p>Are you sure you want to proceed?</p>
|
|
19
|
+
</ui-confirm-dialog>
|
|
20
|
+
`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function destructiveFixture(): Promise<UiConfirmDialog> {
|
|
24
|
+
return fixture(html`<ui-confirm-dialog destructive></ui-confirm-dialog>`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
it('renders with default labels', async () => {
|
|
28
|
+
const element = await basicFixture()
|
|
29
|
+
assert.equal(element.confirmLabel, 'Confirm')
|
|
30
|
+
assert.equal(element.dismissLabel, 'Cancel')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('renders with custom labels', async () => {
|
|
34
|
+
const element = await customLabelsFixture()
|
|
35
|
+
assert.equal(element.confirmLabel, 'Delete')
|
|
36
|
+
assert.equal(element.dismissLabel, 'Keep')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('is modal by default', async () => {
|
|
40
|
+
const element = await basicFixture()
|
|
41
|
+
assert.isTrue(element.modal)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('renders buttons with correct values', async () => {
|
|
45
|
+
const element = await basicFixture()
|
|
46
|
+
await element.updateComplete
|
|
47
|
+
|
|
48
|
+
const dismissButton = element.shadowRoot!.querySelector('ui-button[value="dismiss"]')
|
|
49
|
+
const confirmButton = element.shadowRoot!.querySelector('ui-button[value="confirm"]')
|
|
50
|
+
|
|
51
|
+
assert.ok(dismissButton, 'has dismiss button')
|
|
52
|
+
assert.ok(confirmButton, 'has confirm button')
|
|
53
|
+
assert.equal(dismissButton!.textContent!.trim(), 'Cancel')
|
|
54
|
+
assert.equal(confirmButton!.textContent!.trim(), 'Confirm')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('renders title slot', async () => {
|
|
58
|
+
const element = await withContentFixture()
|
|
59
|
+
await element.updateComplete
|
|
60
|
+
|
|
61
|
+
const titleSlot = element.shadowRoot!.querySelector('slot[name="title"]')
|
|
62
|
+
assert.ok(titleSlot, 'has title slot')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('renders body slot', async () => {
|
|
66
|
+
const element = await withContentFixture()
|
|
67
|
+
await element.updateComplete
|
|
68
|
+
|
|
69
|
+
const bodySlot = element.shadowRoot!.querySelector('slot:not([name])')
|
|
70
|
+
assert.ok(bodySlot, 'has body slot')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('uses filled button for confirm action', async () => {
|
|
74
|
+
const element = await basicFixture()
|
|
75
|
+
await element.updateComplete
|
|
76
|
+
|
|
77
|
+
const confirmButton = element.shadowRoot!.querySelector('ui-button[value="confirm"]')
|
|
78
|
+
assert.equal(confirmButton!.getAttribute('color'), 'filled')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('uses text button for dismiss action', async () => {
|
|
82
|
+
const element = await basicFixture()
|
|
83
|
+
await element.updateComplete
|
|
84
|
+
|
|
85
|
+
const dismissButton = element.shadowRoot!.querySelector('ui-button[value="dismiss"]')
|
|
86
|
+
assert.equal(dismissButton!.getAttribute('color'), 'text')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('renders as destructive dialog', async () => {
|
|
90
|
+
const element = await destructiveFixture()
|
|
91
|
+
await element.updateComplete
|
|
92
|
+
|
|
93
|
+
const confirmButton = element.shadowRoot!.querySelector('ui-button[value="confirm"]')
|
|
94
|
+
assert.ok(confirmButton?.classList.contains('destructive'), 'confirm button has destructive class')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('renders confirm button with destructive styling', async () => {
|
|
98
|
+
const element = await destructiveFixture()
|
|
99
|
+
await element.updateComplete
|
|
100
|
+
|
|
101
|
+
const confirmButton = element.shadowRoot!.querySelector('ui-button[value="confirm"]')
|
|
102
|
+
assert.equal(confirmButton!.getAttribute('color'), 'filled')
|
|
103
|
+
assert.ok(confirmButton!.classList.contains('destructive'), 'has destructive class for styling')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('is not destructive by default', async () => {
|
|
107
|
+
const element = await basicFixture()
|
|
108
|
+
assert.isFalse(element.destructive)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('can be set to destructive', async () => {
|
|
112
|
+
const element = await destructiveFixture()
|
|
113
|
+
assert.isTrue(element.destructive)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('applies destructive class to confirm button when destructive is true', async () => {
|
|
117
|
+
const element = await destructiveFixture()
|
|
118
|
+
await element.updateComplete
|
|
119
|
+
|
|
120
|
+
const confirmButton = element.shadowRoot!.querySelector('ui-button[value="confirm"]')
|
|
121
|
+
assert.isTrue(confirmButton!.classList.contains('destructive'))
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('does not apply destructive class when destructive is false', async () => {
|
|
125
|
+
const element = await basicFixture()
|
|
126
|
+
await element.updateComplete
|
|
127
|
+
|
|
128
|
+
const confirmButton = element.shadowRoot!.querySelector('ui-button[value="confirm"]')
|
|
129
|
+
assert.isFalse(confirmButton!.classList.contains('destructive'))
|
|
130
|
+
})
|
|
131
|
+
})
|
|
@@ -145,6 +145,26 @@ describe('md', () => {
|
|
|
145
145
|
await element.updateComplete
|
|
146
146
|
assert.isTrue(spy.calledOnce)
|
|
147
147
|
})
|
|
148
|
+
|
|
149
|
+
it('applies modal class to dialog when modal is true', async () => {
|
|
150
|
+
const element = await basicFixture()
|
|
151
|
+
element.modal = true
|
|
152
|
+
await element.updateComplete
|
|
153
|
+
|
|
154
|
+
const dialog = element.shadowRoot!.querySelector('dialog') as HTMLDialogElement
|
|
155
|
+
assert.ok(dialog, 'has the dialog element')
|
|
156
|
+
assert.isTrue(dialog.classList.contains('modal'), 'has modal class')
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('applies non-modal class to dialog when modal is false', async () => {
|
|
160
|
+
const element = await basicFixture()
|
|
161
|
+
element.modal = false
|
|
162
|
+
await element.updateComplete
|
|
163
|
+
|
|
164
|
+
const dialog = element.shadowRoot!.querySelector('dialog') as HTMLDialogElement
|
|
165
|
+
assert.ok(dialog, 'has the dialog element')
|
|
166
|
+
assert.isTrue(dialog.classList.contains('non-modal'), 'has non-modal class')
|
|
167
|
+
})
|
|
148
168
|
})
|
|
149
169
|
|
|
150
170
|
describe('icon', () => {
|
|
@@ -229,6 +249,28 @@ describe('md', () => {
|
|
|
229
249
|
const container = element.shadowRoot!.querySelector('.buttons') as HTMLElement
|
|
230
250
|
assert.isTrue(container.classList.contains('with-buttons'), 'has the with-buttons class')
|
|
231
251
|
})
|
|
252
|
+
|
|
253
|
+
it('applies destructive class to confirm button when destructive is true', async () => {
|
|
254
|
+
const element = await basicFixture()
|
|
255
|
+
element.confirmLabel = 'Delete'
|
|
256
|
+
element.destructive = true
|
|
257
|
+
await element.updateComplete
|
|
258
|
+
|
|
259
|
+
const button = element.shadowRoot!.querySelector('.internal-button[value="confirm"]') as UiButton
|
|
260
|
+
assert.ok(button, 'has the confirm button')
|
|
261
|
+
assert.isTrue(button.classList.contains('destructive'), 'has destructive class')
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it('does not apply destructive class when destructive is false', async () => {
|
|
265
|
+
const element = await basicFixture()
|
|
266
|
+
element.confirmLabel = 'Confirm'
|
|
267
|
+
element.destructive = false
|
|
268
|
+
await element.updateComplete
|
|
269
|
+
|
|
270
|
+
const button = element.shadowRoot!.querySelector('.internal-button[value="confirm"]') as UiButton
|
|
271
|
+
assert.ok(button, 'has the confirm button')
|
|
272
|
+
assert.isFalse(button.classList.contains('destructive'), 'does not have destructive class')
|
|
273
|
+
})
|
|
232
274
|
})
|
|
233
275
|
|
|
234
276
|
describe('form handling', () => {
|