@api-client/ui 0.5.2 → 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/elements/highlight/MarkdownStyles.d.ts.map +1 -1
- package/build/src/elements/highlight/MarkdownStyles.js +7 -11
- package/build/src/elements/highlight/MarkdownStyles.js.map +1 -1
- 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/eslint.config.js +6 -6
- package/package.json +7 -2
- package/scripts/release.js +66 -0
- package/src/elements/highlight/MarkdownStyles.ts +7 -11
- 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
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# UI Confirm Dialog
|
|
2
|
+
|
|
3
|
+
A simple Material Design 3 styled | Property | Type | Default | Description |
|
|
4
|
+
|----------|------|---------|-------------|
|
|
5
|
+
| `open` | `boolean` | `false` | Controls dialog visibility |
|
|
6
|
+
| `modal` | `boolean` | `true` | Whether dialog is modal (set by default) |
|
|
7
|
+
| `confirmLabel` | `string` | `'Confirm'` | Text for the confirm button |
|
|
8
|
+
| `dismissLabel` | `string` | `'Cancel'` | Text for the dismiss button |
|
|
9
|
+
| `destructive` | `boolean` | `false` | Styles confirm button with error colors for destructive actions |m dialog component for confirming user actions.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Material Design 3 Styling**: Follows MD3 design tokens and patterns
|
|
14
|
+
- **Customizable Button Labels**: Configure confirm and dismiss button text
|
|
15
|
+
- **Slot-based Content**: Flexible content structure with title and body slots
|
|
16
|
+
- **Modal by Default**: Designed for confirmation workflows
|
|
17
|
+
- **Accessible**: Supports keyboard navigation and screen readers
|
|
18
|
+
- **Built-in Event Handling**: Dispatches close events with detailed reason information
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Basic Usage
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<ui-confirm-dialog .open="${showDialog}" @close="${handleClose}">
|
|
26
|
+
<span slot="title">Confirm Action</span>
|
|
27
|
+
<p>Are you sure you want to proceed with this action?</p>
|
|
28
|
+
</ui-confirm-dialog>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Custom Button Labels
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<ui-confirm-dialog
|
|
35
|
+
confirmLabel="Delete"
|
|
36
|
+
dismissLabel="Keep"
|
|
37
|
+
destructive
|
|
38
|
+
.open="${showDialog}"
|
|
39
|
+
@close="${handleClose}"
|
|
40
|
+
>
|
|
41
|
+
<span slot="title">Delete Item</span>
|
|
42
|
+
<p>Are you sure you want to delete this item? This action cannot be undone.</p>
|
|
43
|
+
</ui-confirm-dialog>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Event Handling
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
function handleClose(e) {
|
|
50
|
+
const { cancelled, value } = e.detail;
|
|
51
|
+
if (!cancelled) {
|
|
52
|
+
// User confirmed the action
|
|
53
|
+
performAction();
|
|
54
|
+
} else {
|
|
55
|
+
// User dismissed the dialog
|
|
56
|
+
console.log('Action cancelled');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Properties
|
|
62
|
+
|
|
63
|
+
| Property | Type | Default | Description |
|
|
64
|
+
|----------|------|---------|-------------|
|
|
65
|
+
| `open` | `boolean` | `false` | Controls dialog visibility |
|
|
66
|
+
| `modal` | `boolean` | `true` | Whether dialog is modal (set by default) |
|
|
67
|
+
| `confirmLabel` | `string` | `'Confirm'` | Text for the confirm button |
|
|
68
|
+
| `dismissLabel` | `string` | `'Cancel'` | Text for the dismiss button |
|
|
69
|
+
|
|
70
|
+
## Slots
|
|
71
|
+
|
|
72
|
+
| Slot | Description |
|
|
73
|
+
|------|-------------|
|
|
74
|
+
| `title` | Dialog title content |
|
|
75
|
+
| (default) | Main body content |
|
|
76
|
+
|
|
77
|
+
## Events
|
|
78
|
+
|
|
79
|
+
| Event | Detail Type | Description |
|
|
80
|
+
|-------|-------------|-------------|
|
|
81
|
+
| `close` | `UiDialogClosingReason` | Fired when dialog is closed |
|
|
82
|
+
|
|
83
|
+
### UiDialogClosingReason
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
interface UiDialogClosingReason {
|
|
87
|
+
cancelled: boolean; // true if dismissed, false if confirmed
|
|
88
|
+
value?: unknown; // optional value associated with the action
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Styling
|
|
93
|
+
|
|
94
|
+
The component uses Material Design 3 design tokens and can be styled using CSS custom properties:
|
|
95
|
+
|
|
96
|
+
```css
|
|
97
|
+
ui-confirm-dialog {
|
|
98
|
+
--ui-dialog-max-width: 400px;
|
|
99
|
+
--ui-dialog-max-height: 300px;
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Design Patterns
|
|
104
|
+
|
|
105
|
+
### Confirmation Workflows
|
|
106
|
+
|
|
107
|
+
Use this dialog when you need users to explicitly confirm an action:
|
|
108
|
+
|
|
109
|
+
- Deleting items
|
|
110
|
+
- Saving changes
|
|
111
|
+
- Leaving unsaved work
|
|
112
|
+
- Performing destructive actions
|
|
113
|
+
|
|
114
|
+
### Button Styling
|
|
115
|
+
|
|
116
|
+
The component follows Material Design patterns:
|
|
117
|
+
|
|
118
|
+
- **Dismiss button**: Text style (lower emphasis)
|
|
119
|
+
- **Confirm button**: Filled style (higher emphasis)
|
|
120
|
+
|
|
121
|
+
### Content Guidelines
|
|
122
|
+
|
|
123
|
+
- **Title**: Keep concise, use action-oriented language
|
|
124
|
+
- **Body**: Explain the consequence of the action
|
|
125
|
+
- **Button Labels**: Use specific, clear language
|
|
126
|
+
|
|
127
|
+
## Examples
|
|
128
|
+
|
|
129
|
+
### Delete Confirmation
|
|
130
|
+
|
|
131
|
+
```html
|
|
132
|
+
<ui-confirm-dialog
|
|
133
|
+
confirmLabel="Delete"
|
|
134
|
+
dismissLabel="Keep"
|
|
135
|
+
destructive
|
|
136
|
+
.open="${showDelete}"
|
|
137
|
+
>
|
|
138
|
+
<span slot="title">Delete Item</span>
|
|
139
|
+
<p>Are you sure you want to delete this item?</p>
|
|
140
|
+
<p><strong>This action cannot be undone.</strong></p>
|
|
141
|
+
</ui-confirm-dialog>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Save Changes
|
|
145
|
+
|
|
146
|
+
```html
|
|
147
|
+
<ui-confirm-dialog
|
|
148
|
+
confirmLabel="Save & Exit"
|
|
149
|
+
dismissLabel="Continue Editing"
|
|
150
|
+
.open="${showSave}"
|
|
151
|
+
>
|
|
152
|
+
<span slot="title">Save Changes</span>
|
|
153
|
+
<p>You have unsaved changes in your document.</p>
|
|
154
|
+
<p>Would you like to save your changes before exiting?</p>
|
|
155
|
+
</ui-confirm-dialog>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Logout Confirmation
|
|
159
|
+
|
|
160
|
+
```html
|
|
161
|
+
<ui-confirm-dialog
|
|
162
|
+
confirmLabel="Logout"
|
|
163
|
+
dismissLabel="Stay Logged In"
|
|
164
|
+
.open="${showLogout}"
|
|
165
|
+
>
|
|
166
|
+
<span slot="title">Logout</span>
|
|
167
|
+
<p>Are you sure you want to logout?</p>
|
|
168
|
+
</ui-confirm-dialog>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Destructive Actions
|
|
172
|
+
|
|
173
|
+
Use the `destructive` attribute for actions that are permanent or could cause data loss:
|
|
174
|
+
|
|
175
|
+
- Deleting items
|
|
176
|
+
- Removing users
|
|
177
|
+
- Clearing data
|
|
178
|
+
- Resetting settings
|
|
179
|
+
|
|
180
|
+
The destructive styling uses Material Design error colors to provide clear visual feedback that the action is potentially harmful.
|
|
181
|
+
|
|
182
|
+
```html
|
|
183
|
+
<ui-confirm-dialog
|
|
184
|
+
confirmLabel="Delete Account"
|
|
185
|
+
dismissLabel="Cancel"
|
|
186
|
+
destructive
|
|
187
|
+
.open="${showDeleteAccount}"
|
|
188
|
+
>
|
|
189
|
+
<span slot="title">Delete Account</span>
|
|
190
|
+
<p>This will permanently delete your account and all associated data.</p>
|
|
191
|
+
<p><strong>This action cannot be undone.</strong></p>
|
|
192
|
+
</ui-confirm-dialog>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Accessibility
|
|
196
|
+
|
|
197
|
+
The component includes:
|
|
198
|
+
|
|
199
|
+
- Proper ARIA attributes
|
|
200
|
+
- Keyboard navigation support (ESC to dismiss)
|
|
201
|
+
- Focus management
|
|
202
|
+
- Screen reader compatibility
|
|
203
|
+
|
|
204
|
+
## Integration
|
|
205
|
+
|
|
206
|
+
To use in your project:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import '../path/to/ui-confirm-dialog.js';
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The component is available as `<ui-confirm-dialog>` in your HTML templates.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { TemplateResult, html } from 'lit'
|
|
2
|
+
import { ClassInfo, classMap } from 'lit/directives/class-map.js'
|
|
3
|
+
import UiDialog from './Dialog.js'
|
|
4
|
+
|
|
5
|
+
import '../../button/ui-button.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A simple Material Design 3 styled confirm dialog for confirming user actions.
|
|
9
|
+
*
|
|
10
|
+
* This dialog provides a clean way to ask users to confirm or dismiss an action.
|
|
11
|
+
* It supports customizable button labels and content through slots.
|
|
12
|
+
*
|
|
13
|
+
* **Usage Example:**
|
|
14
|
+
* ```html
|
|
15
|
+
* <ui-confirm-dialog modal .open="${this.showConfirm}" @close="${this.handleConfirmClose}">
|
|
16
|
+
* <span slot="title">Delete Item</span>
|
|
17
|
+
* <p>Are you sure you want to delete this item? This action cannot be undone.</p>
|
|
18
|
+
* </ui-confirm-dialog>
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* **Event Handling:**
|
|
22
|
+
* Listen for the `close` event to handle user interaction:
|
|
23
|
+
* ```javascript
|
|
24
|
+
* dialog.addEventListener('close', (e) => {
|
|
25
|
+
* const { cancelled } = e.detail;
|
|
26
|
+
* if (!cancelled) {
|
|
27
|
+
* // User confirmed the action
|
|
28
|
+
* performAction();
|
|
29
|
+
* }
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @slot title - The dialog title content
|
|
34
|
+
* @slot - The main body content of the dialog
|
|
35
|
+
* @fires close - Dispatched when the dialog is closed with closing reason details
|
|
36
|
+
*/
|
|
37
|
+
export default class ConfirmDialog extends UiDialog {
|
|
38
|
+
constructor() {
|
|
39
|
+
super()
|
|
40
|
+
// Set modal by default for confirm dialogs
|
|
41
|
+
this.modal = true
|
|
42
|
+
this.confirmLabel = 'Confirm'
|
|
43
|
+
this.dismissLabel = 'Cancel'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
protected override renderContent(): TemplateResult[] {
|
|
47
|
+
return [this.renderTitle(), this.renderBody(), this.renderButtons()]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
protected override renderButtons(): TemplateResult {
|
|
51
|
+
const classes: ClassInfo = {
|
|
52
|
+
'buttons': true,
|
|
53
|
+
'with-buttons': true,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return html`
|
|
57
|
+
<div class="${classMap(classes)}" part="buttons">
|
|
58
|
+
<ui-button
|
|
59
|
+
color="text"
|
|
60
|
+
value="dismiss"
|
|
61
|
+
class="internal-button"
|
|
62
|
+
@click="${this.handleDismiss}"
|
|
63
|
+
part="dismiss-button"
|
|
64
|
+
>
|
|
65
|
+
${this.dismissLabel}
|
|
66
|
+
</ui-button>
|
|
67
|
+
<ui-button
|
|
68
|
+
color="filled"
|
|
69
|
+
value="confirm"
|
|
70
|
+
class="internal-button ${this.destructive ? 'destructive' : ''}"
|
|
71
|
+
@click="${this.handleConfirm}"
|
|
72
|
+
part="confirm-button"
|
|
73
|
+
>
|
|
74
|
+
${this.confirmLabel}
|
|
75
|
+
</ui-button>
|
|
76
|
+
</div>
|
|
77
|
+
`
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -28,6 +28,20 @@ export default css`
|
|
|
28
28
|
flex-direction: column;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/* Positioning for non-modal dialogs */
|
|
32
|
+
dialog.non-modal {
|
|
33
|
+
position: fixed;
|
|
34
|
+
top: 50%;
|
|
35
|
+
left: 50%;
|
|
36
|
+
transform: translate(-50%, -50%);
|
|
37
|
+
margin: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
dialog.non-modal:open {
|
|
41
|
+
/* Override the animation transform for non-modal dialogs to account for centering */
|
|
42
|
+
animation: 250ms cubic-bezier(0.2, 0, 0, 1) show-non-modal-dialog;
|
|
43
|
+
}
|
|
44
|
+
|
|
31
45
|
dialog:open::backdrop {
|
|
32
46
|
animation: 250ms cubic-bezier(0.2, 0, 0, 1) show-backdrop;
|
|
33
47
|
}
|
|
@@ -35,7 +49,6 @@ export default css`
|
|
|
35
49
|
.container {
|
|
36
50
|
display: flex;
|
|
37
51
|
flex-direction: column;
|
|
38
|
-
overflow: hidden;
|
|
39
52
|
flex: 1;
|
|
40
53
|
}
|
|
41
54
|
|
|
@@ -57,6 +70,11 @@ export default css`
|
|
|
57
70
|
height: 24px;
|
|
58
71
|
}
|
|
59
72
|
|
|
73
|
+
.icon.destructive ::slotted(*) {
|
|
74
|
+
color: var(--md-sys-color-error);
|
|
75
|
+
fill: var(--md-sys-color-error);
|
|
76
|
+
}
|
|
77
|
+
|
|
60
78
|
.title {
|
|
61
79
|
display: none;
|
|
62
80
|
color: var(--md-sys-color-on-surface);
|
|
@@ -114,6 +132,15 @@ export default css`
|
|
|
114
132
|
}
|
|
115
133
|
}
|
|
116
134
|
|
|
135
|
+
@keyframes show-non-modal-dialog {
|
|
136
|
+
from {
|
|
137
|
+
transform: translate(-50%, -50%) translateY(-110%) scaleY(0);
|
|
138
|
+
}
|
|
139
|
+
to {
|
|
140
|
+
transform: translate(-50%, -50%) translateY(0%) scaleY(1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
117
144
|
@keyframes show-backdrop {
|
|
118
145
|
from {
|
|
119
146
|
opacity: 0;
|
|
@@ -122,4 +149,15 @@ export default css`
|
|
|
122
149
|
opacity: 1;
|
|
123
150
|
}
|
|
124
151
|
}
|
|
152
|
+
|
|
153
|
+
/* Destructive button styling for dangerous actions */
|
|
154
|
+
.internal-button.destructive {
|
|
155
|
+
--_background-color: var(--md-sys-color-error);
|
|
156
|
+
--_color: var(--md-sys-color-on-error);
|
|
157
|
+
|
|
158
|
+
/* Override ripple colors for better interaction feedback */
|
|
159
|
+
--md-ripple-hover-state-layer-color: var(--md-sys-color-on-error);
|
|
160
|
+
--md-ripple-focus-state-layer-color: var(--md-sys-color-on-error);
|
|
161
|
+
--md-ripple-pressed-state-layer-color: var(--md-sys-color-on-error);
|
|
162
|
+
}
|
|
125
163
|
`
|
|
@@ -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', () => {
|