@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.
Files changed (28) hide show
  1. package/build/src/md/dialog/internals/ConfirmDialog.d.ts +39 -0
  2. package/build/src/md/dialog/internals/ConfirmDialog.d.ts.map +1 -0
  3. package/build/src/md/dialog/internals/ConfirmDialog.js +75 -0
  4. package/build/src/md/dialog/internals/ConfirmDialog.js.map +1 -0
  5. package/build/src/md/dialog/internals/Dialog.d.ts +6 -0
  6. package/build/src/md/dialog/internals/Dialog.d.ts.map +1 -1
  7. package/build/src/md/dialog/internals/Dialog.js +43 -5
  8. package/build/src/md/dialog/internals/Dialog.js.map +1 -1
  9. package/build/src/md/dialog/internals/Dialog.styles.d.ts.map +1 -1
  10. package/build/src/md/dialog/internals/Dialog.styles.js +39 -1
  11. package/build/src/md/dialog/internals/Dialog.styles.js.map +1 -1
  12. package/build/src/md/dialog/ui-confirm-dialog.d.ts +48 -0
  13. package/build/src/md/dialog/ui-confirm-dialog.d.ts.map +1 -0
  14. package/build/src/md/dialog/ui-confirm-dialog.js +64 -0
  15. package/build/src/md/dialog/ui-confirm-dialog.js.map +1 -0
  16. package/demo/md/dialog/confirm-dialog.html +49 -0
  17. package/demo/md/dialog/confirm-dialog.ts +121 -0
  18. package/demo/md/dialog/dialog.ts +76 -1
  19. package/demo/md/index.html +2 -0
  20. package/package.json +1 -1
  21. package/src/md/dialog/README.md +212 -0
  22. package/src/md/dialog/internals/ConfirmDialog.ts +79 -0
  23. package/src/md/dialog/internals/Dialog.styles.ts +39 -1
  24. package/src/md/dialog/internals/Dialog.ts +17 -4
  25. package/src/md/dialog/ui-confirm-dialog.ts +52 -0
  26. package/test/README.md +2 -0
  27. package/test/md/dialog/UiConfirmDialog.test.ts +131 -0
  28. 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="internal-button"
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', () => {