@pine-ds/core 3.5.2 → 3.6.0

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 (175) hide show
  1. package/components/mock-pds-modal.js +9 -4
  2. package/components/mock-pds-modal.js.map +1 -1
  3. package/components/pds-box2.js +132 -2
  4. package/components/pds-box2.js.map +1 -1
  5. package/components/pds-button2.js +51 -2
  6. package/components/pds-button2.js.map +1 -1
  7. package/components/pds-modal-content.js +128 -11
  8. package/components/pds-modal-content.js.map +1 -1
  9. package/components/pds-modal.js +13 -3
  10. package/components/pds-modal.js.map +1 -1
  11. package/components/pds-textarea.js +111 -7
  12. package/components/pds-textarea.js.map +1 -1
  13. package/components/pds-toast.js +3 -3
  14. package/components/pds-tooltip.js +2 -2
  15. package/dist/cjs/loader.cjs.js +1 -1
  16. package/dist/cjs/mock-pds-modal.cjs.entry.js +8 -4
  17. package/dist/cjs/mock-pds-modal.cjs.entry.js.map +1 -1
  18. package/dist/cjs/mock-pds-modal.entry.cjs.js.map +1 -1
  19. package/dist/cjs/pds-box.cjs.entry.js +67 -2
  20. package/dist/cjs/pds-box.cjs.entry.js.map +1 -1
  21. package/dist/cjs/pds-box.entry.cjs.js.map +1 -1
  22. package/dist/cjs/pds-button.cjs.entry.js +50 -1
  23. package/dist/cjs/pds-button.cjs.entry.js.map +1 -1
  24. package/dist/cjs/pds-button.entry.cjs.js.map +1 -1
  25. package/dist/cjs/pds-modal-content.cjs.entry.js +128 -11
  26. package/dist/cjs/pds-modal-content.cjs.entry.js.map +1 -1
  27. package/dist/cjs/pds-modal-content.entry.cjs.js.map +1 -1
  28. package/dist/cjs/pds-modal.cjs.entry.js +12 -3
  29. package/dist/cjs/pds-modal.cjs.entry.js.map +1 -1
  30. package/dist/cjs/pds-modal.entry.cjs.js.map +1 -1
  31. package/dist/cjs/pds-textarea.cjs.entry.js +108 -6
  32. package/dist/cjs/pds-textarea.cjs.entry.js.map +1 -1
  33. package/dist/cjs/pds-textarea.entry.cjs.js.map +1 -1
  34. package/dist/cjs/pds-toast.cjs.entry.js +3 -3
  35. package/dist/cjs/pds-tooltip.cjs.entry.js +2 -2
  36. package/dist/cjs/pine-core.cjs.js +1 -1
  37. package/dist/collection/components/pds-box/pds-box.css +1325 -0
  38. package/dist/collection/components/pds-box/pds-box.js +1913 -148
  39. package/dist/collection/components/pds-box/pds-box.js.map +1 -1
  40. package/dist/collection/components/pds-button/pds-button.js +59 -1
  41. package/dist/collection/components/pds-button/pds-button.js.map +1 -1
  42. package/dist/collection/components/pds-modal/pds-modal-content/pds-modal-content.css +4 -6
  43. package/dist/collection/components/pds-modal/pds-modal-content/pds-modal-content.js +128 -11
  44. package/dist/collection/components/pds-modal/pds-modal-content/pds-modal-content.js.map +1 -1
  45. package/dist/collection/components/pds-modal/pds-modal.css +42 -1
  46. package/dist/collection/components/pds-modal/pds-modal.js +34 -2
  47. package/dist/collection/components/pds-modal/pds-modal.js.map +1 -1
  48. package/dist/collection/components/pds-modal/stories/pds-modal.stories.js +245 -0
  49. package/dist/collection/components/pds-modal/test/mock-pds-modal.js +30 -3
  50. package/dist/collection/components/pds-modal/test/mock-pds-modal.js.map +1 -1
  51. package/dist/collection/components/pds-textarea/pds-textarea.css +51 -11
  52. package/dist/collection/components/pds-textarea/pds-textarea.js +127 -4
  53. package/dist/collection/components/pds-textarea/pds-textarea.js.map +1 -1
  54. package/dist/collection/components/pds-textarea/stories/pds-textarea.stories.js +13 -0
  55. package/dist/collection/components/pds-toast/pds-toast.js +3 -3
  56. package/dist/collection/components/pds-tooltip/pds-tooltip.js +2 -2
  57. package/dist/docs.json +3988 -380
  58. package/dist/esm/loader.js +1 -1
  59. package/dist/esm/mock-pds-modal.entry.js +8 -4
  60. package/dist/esm/mock-pds-modal.entry.js.map +1 -1
  61. package/dist/esm/pds-box.entry.js +67 -2
  62. package/dist/esm/pds-box.entry.js.map +1 -1
  63. package/dist/esm/pds-button.entry.js +50 -1
  64. package/dist/esm/pds-button.entry.js.map +1 -1
  65. package/dist/esm/pds-modal-content.entry.js +128 -11
  66. package/dist/esm/pds-modal-content.entry.js.map +1 -1
  67. package/dist/esm/pds-modal.entry.js +12 -3
  68. package/dist/esm/pds-modal.entry.js.map +1 -1
  69. package/dist/esm/pds-textarea.entry.js +108 -6
  70. package/dist/esm/pds-textarea.entry.js.map +1 -1
  71. package/dist/esm/pds-toast.entry.js +3 -3
  72. package/dist/esm/pds-tooltip.entry.js +2 -2
  73. package/dist/esm/pine-core.js +1 -1
  74. package/dist/esm-es5/loader.js +1 -1
  75. package/dist/esm-es5/mock-pds-modal.entry.js +1 -1
  76. package/dist/esm-es5/mock-pds-modal.entry.js.map +1 -1
  77. package/dist/esm-es5/pds-box.entry.js +1 -1
  78. package/dist/esm-es5/pds-box.entry.js.map +1 -1
  79. package/dist/esm-es5/pds-button.entry.js +1 -1
  80. package/dist/esm-es5/pds-button.entry.js.map +1 -1
  81. package/dist/esm-es5/pds-modal-content.entry.js +1 -1
  82. package/dist/esm-es5/pds-modal-content.entry.js.map +1 -1
  83. package/dist/esm-es5/pds-modal.entry.js +1 -1
  84. package/dist/esm-es5/pds-modal.entry.js.map +1 -1
  85. package/dist/esm-es5/pds-textarea.entry.js +1 -1
  86. package/dist/esm-es5/pds-textarea.entry.js.map +1 -1
  87. package/dist/esm-es5/pds-toast.entry.js +1 -1
  88. package/dist/esm-es5/pds-tooltip.entry.js +1 -1
  89. package/dist/esm-es5/pine-core.js +1 -1
  90. package/dist/pine-core/mock-pds-modal.entry.esm.js.map +1 -1
  91. package/dist/pine-core/p-318fd0cf.entry.js +2 -0
  92. package/dist/pine-core/p-318fd0cf.entry.js.map +1 -0
  93. package/dist/pine-core/{p-c4d6fe50.system.entry.js → p-346561a6.system.entry.js} +2 -2
  94. package/dist/pine-core/p-346561a6.system.entry.js.map +1 -0
  95. package/dist/pine-core/p-3gYSFJIn.system.js.map +1 -0
  96. package/dist/pine-core/{p-IG5YumI3.system.js.map → p-3pEJO0vO.system.js.map} +1 -1
  97. package/dist/pine-core/p-41d1b164.entry.js +2 -0
  98. package/dist/pine-core/p-41d1b164.entry.js.map +1 -0
  99. package/dist/pine-core/{p-02326ac3.entry.js → p-4c81420c.entry.js} +2 -2
  100. package/dist/pine-core/p-52cb152b.system.entry.js +2 -0
  101. package/dist/pine-core/p-52cb152b.system.entry.js.map +1 -0
  102. package/dist/pine-core/p-540cfd70.entry.js +2 -0
  103. package/dist/pine-core/p-540cfd70.entry.js.map +1 -0
  104. package/dist/pine-core/{p-11d4036e.entry.js → p-5f5b19f4.entry.js} +2 -2
  105. package/dist/pine-core/p-6d4d4705.system.entry.js +2 -0
  106. package/dist/pine-core/p-6d4d4705.system.entry.js.map +1 -0
  107. package/dist/pine-core/{p-68b5665a.entry.js → p-84949a12.entry.js} +2 -2
  108. package/dist/pine-core/p-84949a12.entry.js.map +1 -0
  109. package/dist/pine-core/{p-7a95a90f.system.entry.js → p-8726c99d.system.entry.js} +2 -2
  110. package/dist/pine-core/{p-2kXtbFXu.system.js.map → p-BZoPY2dP.system.js.map} +1 -1
  111. package/dist/pine-core/p-C0zqu7Gr.system.js.map +1 -0
  112. package/dist/pine-core/p-CHJgq_z7.system.js.map +1 -0
  113. package/dist/pine-core/p-Co5XZmTN.system.js.map +1 -0
  114. package/dist/pine-core/p-Czoq9yJM.system.js.map +1 -0
  115. package/dist/pine-core/p-De9tROL-.system.js +1 -1
  116. package/dist/pine-core/p-WkrM7Vv0.system.js.map +1 -0
  117. package/dist/pine-core/p-a9895385.system.entry.js +2 -0
  118. package/dist/pine-core/p-a9895385.system.entry.js.map +1 -0
  119. package/dist/pine-core/p-c0df3222.entry.js +2 -0
  120. package/dist/pine-core/p-c0df3222.entry.js.map +1 -0
  121. package/dist/pine-core/{p-884b9ae6.system.entry.js → p-d8d8fe07.system.entry.js} +2 -2
  122. package/dist/pine-core/p-dc19ce6c.system.entry.js +2 -0
  123. package/dist/pine-core/p-dc19ce6c.system.entry.js.map +1 -0
  124. package/dist/pine-core/p-e08f492a.entry.js +2 -0
  125. package/dist/pine-core/p-e08f492a.entry.js.map +1 -0
  126. package/dist/pine-core/p-fcb39155.system.entry.js +2 -0
  127. package/dist/pine-core/p-fcb39155.system.entry.js.map +1 -0
  128. package/dist/pine-core/pds-box.entry.esm.js.map +1 -1
  129. package/dist/pine-core/pds-button.entry.esm.js.map +1 -1
  130. package/dist/pine-core/pds-modal-content.entry.esm.js.map +1 -1
  131. package/dist/pine-core/pds-modal.entry.esm.js.map +1 -1
  132. package/dist/pine-core/pds-textarea.entry.esm.js.map +1 -1
  133. package/dist/pine-core/pine-core.esm.js +1 -1
  134. package/dist/types/components/pds-box/pds-box.d.ts +315 -0
  135. package/dist/types/components/pds-button/pds-button.d.ts +4 -0
  136. package/dist/types/components/pds-modal/pds-modal-content/pds-modal-content.d.ts +18 -1
  137. package/dist/types/components/pds-modal/pds-modal.d.ts +5 -0
  138. package/dist/types/components/pds-modal/test/mock-pds-modal.d.ts +5 -0
  139. package/dist/types/components/pds-textarea/pds-textarea.d.ts +20 -0
  140. package/dist/types/components.d.ts +660 -2
  141. package/hydrate/index.js +448 -34
  142. package/hydrate/index.mjs +448 -34
  143. package/package.json +2 -2
  144. package/dist/pine-core/p-007b61e9.system.entry.js +0 -2
  145. package/dist/pine-core/p-007b61e9.system.entry.js.map +0 -1
  146. package/dist/pine-core/p-0485aa93.system.entry.js +0 -2
  147. package/dist/pine-core/p-0485aa93.system.entry.js.map +0 -1
  148. package/dist/pine-core/p-2186e5d4.system.entry.js +0 -2
  149. package/dist/pine-core/p-2186e5d4.system.entry.js.map +0 -1
  150. package/dist/pine-core/p-54061a33.entry.js +0 -2
  151. package/dist/pine-core/p-54061a33.entry.js.map +0 -1
  152. package/dist/pine-core/p-68b5665a.entry.js.map +0 -1
  153. package/dist/pine-core/p-96a89cd5.system.entry.js +0 -2
  154. package/dist/pine-core/p-96a89cd5.system.entry.js.map +0 -1
  155. package/dist/pine-core/p-Bv5PJxD8.system.js.map +0 -1
  156. package/dist/pine-core/p-CD_nPb2F.system.js.map +0 -1
  157. package/dist/pine-core/p-DJekRkSL.system.js.map +0 -1
  158. package/dist/pine-core/p-DNYl_6t_.system.js.map +0 -1
  159. package/dist/pine-core/p-Dt10r3RZ.system.js.map +0 -1
  160. package/dist/pine-core/p-IIl2cTlj.system.js.map +0 -1
  161. package/dist/pine-core/p-a24c46e4.entry.js +0 -2
  162. package/dist/pine-core/p-a24c46e4.entry.js.map +0 -1
  163. package/dist/pine-core/p-c4d6fe50.system.entry.js.map +0 -1
  164. package/dist/pine-core/p-c6badcf8.entry.js +0 -2
  165. package/dist/pine-core/p-c6badcf8.entry.js.map +0 -1
  166. package/dist/pine-core/p-d714f68f.system.entry.js +0 -2
  167. package/dist/pine-core/p-d714f68f.system.entry.js.map +0 -1
  168. package/dist/pine-core/p-e5ca5b8e.entry.js +0 -2
  169. package/dist/pine-core/p-e5ca5b8e.entry.js.map +0 -1
  170. package/dist/pine-core/p-fb4058e6.entry.js +0 -2
  171. package/dist/pine-core/p-fb4058e6.entry.js.map +0 -1
  172. /package/dist/pine-core/{p-02326ac3.entry.js.map → p-4c81420c.entry.js.map} +0 -0
  173. /package/dist/pine-core/{p-11d4036e.entry.js.map → p-5f5b19f4.entry.js.map} +0 -0
  174. /package/dist/pine-core/{p-7a95a90f.system.entry.js.map → p-8726c99d.system.entry.js.map} +0 -0
  175. /package/dist/pine-core/{p-884b9ae6.system.entry.js.map → p-d8d8fe07.system.entry.js.map} +0 -0
@@ -40,18 +40,36 @@ pds-modal .pds-modal__backdrop {
40
40
  display: flex;
41
41
  flex-direction: column;
42
42
  margin: var(--pine-dimension-md);
43
- max-height: calc(100vh - (5vh + 96px));
43
+ max-height: none;
44
44
  width: 100%;
45
45
  }
46
+ .pds-modal.pds-modal--scrollable {
47
+ max-height: calc(100vh - (5vh + 96px));
48
+ }
49
+ @supports (height: 100dvh) {
50
+ .pds-modal.pds-modal--scrollable {
51
+ max-height: calc(100dvh - (5dvh + 96px));
52
+ }
53
+ }
46
54
  @media (min-width: 992px) {
47
55
  .pds-modal {
48
56
  margin-block-start: 6vh;
49
57
  }
58
+ @supports (height: 100dvh) {
59
+ .pds-modal {
60
+ margin-block-start: 6dvh;
61
+ }
62
+ }
50
63
  }
51
64
  @media (min-width: 1200px) {
52
65
  .pds-modal {
53
66
  margin-block-start: 8vh;
54
67
  }
68
+ @supports (height: 100dvh) {
69
+ .pds-modal {
70
+ margin-block-start: 8dvh;
71
+ }
72
+ }
55
73
  }
56
74
 
57
75
  .pds-modal--sm {
@@ -73,7 +91,30 @@ pds-modal .pds-modal__backdrop {
73
91
  max-height: 100vh;
74
92
  max-width: 100%;
75
93
  }
94
+ @supports (height: 100dvh) {
95
+ .pds-modal--fullscreen {
96
+ max-height: 100dvh;
97
+ }
98
+ }
99
+ .pds-modal--fullscreen.pds-modal--scrollable {
100
+ max-height: 100vh;
101
+ }
102
+ @supports (height: 100dvh) {
103
+ .pds-modal--fullscreen.pds-modal--scrollable {
104
+ max-height: 100dvh;
105
+ }
106
+ }
76
107
 
77
108
  .pds-modal--fullscreen .pds-modal-content {
78
109
  flex: 1;
110
+ }
111
+
112
+ .pds-modal--scrollable pds-modal-content {
113
+ border-block-end: 1px solid transparent;
114
+ border-block-start: 1px solid transparent;
115
+ overflow-y: auto;
116
+ }
117
+
118
+ .pds-modal:not(.pds-modal--scrollable) pds-modal-content {
119
+ overflow-y: visible;
79
120
  }
@@ -17,6 +17,11 @@ export class PdsModal {
17
17
  * @default 'md'
18
18
  */
19
19
  this.size = 'md';
20
+ /**
21
+ * Whether the modal content should be scrollable
22
+ * @default true
23
+ */
24
+ this.scrollable = true;
20
25
  /**
21
26
  * Stores the list of focusable elements in the modal
22
27
  */
@@ -227,10 +232,14 @@ export class PdsModal {
227
232
  return thisZIndex === maxZIndex;
228
233
  }
229
234
  render() {
230
- return (h("dialog", { key: 'd8521939e6a88f64e1787840aa99fd1ec5899254', class: {
235
+ return (h("dialog", { key: '57daacecb0d85c643c1fe17b98046dcc98f982cb', class: {
231
236
  'pds-modal__backdrop': true,
232
237
  'open': this.open
233
- }, "aria-modal": "true", "aria-labelledby": `${this.componentId}-heading`, onClick: this.handleBackdropClick }, h("div", { key: '89b838aa26621957de1caf77c27be855d17acb0a', class: `pds-modal pds-modal--${this.size} pds-modal--scrollable` }, h("slot", { key: '107062efee2afb25eddba26d0d468695401dd5ba' }))));
238
+ }, "aria-modal": "true", "aria-labelledby": `${this.componentId}-heading`, onClick: this.handleBackdropClick }, h("div", { key: '9fe239071cb2679dd626bc3cf8fd94abe9c1bee0', class: {
239
+ 'pds-modal': true,
240
+ [`pds-modal--${this.size}`]: true,
241
+ 'pds-modal--scrollable': this.scrollable
242
+ } }, h("slot", { key: '59edffec7f550550a03ce2100eb3aeb2be99902b' }))));
234
243
  }
235
244
  static get is() { return "pds-modal"; }
236
245
  static get originalStyleUrls() {
@@ -332,6 +341,29 @@ export class PdsModal {
332
341
  "attribute": "size",
333
342
  "reflect": false,
334
343
  "defaultValue": "'md'"
344
+ },
345
+ "scrollable": {
346
+ "type": "boolean",
347
+ "mutable": false,
348
+ "complexType": {
349
+ "original": "boolean",
350
+ "resolved": "boolean",
351
+ "references": {}
352
+ },
353
+ "required": false,
354
+ "optional": false,
355
+ "docs": {
356
+ "tags": [{
357
+ "name": "default",
358
+ "text": "true"
359
+ }],
360
+ "text": "Whether the modal content should be scrollable"
361
+ },
362
+ "getter": false,
363
+ "setter": false,
364
+ "attribute": "scrollable",
365
+ "reflect": false,
366
+ "defaultValue": "true"
335
367
  }
336
368
  };
337
369
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pds-modal.js","sourceRoot":"","sources":["../../../src/components/pds-modal/pds-modal.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Component, Element, Event, EventEmitter, h, Method, Prop, State, Watch } from '@stencil/core';\n\n@Component({\n tag: 'pds-modal',\n styleUrl: 'pds-modal.scss',\n shadow: false\n})\nexport class PdsModal {\n private modalRef: HTMLDialogElement;\n private previousActiveElement: HTMLElement;\n private focusableElements: HTMLElement[] = [];\n\n @Element() el: HTMLPdsModalElement;\n\n /**\n * Whether the modal can be dismissed by clicking the backdrop\n * @default true\n */\n @Prop() backdropDismiss = true;\n\n /**\n * A unique identifier used for the underlying component `id` attribute.\n */\n @Prop() componentId: string;\n\n /**\n * Whether the modal is open\n * @default false\n */\n @Prop({ mutable: true }) open = false;\n\n /**\n * The size of the modal\n * @default 'md'\n */\n @Prop() size: 'sm' | 'md' | 'lg' | 'fullscreen' = 'md';\n\n // Modal content is always scrollable by default\n\n /**\n * Emitted when the modal is opened\n */\n @Event() pdsModalOpen: EventEmitter<void>;\n\n /**\n * Emitted when the modal is closed\n */\n @Event() pdsModalClose: EventEmitter<void>;\n\n /**\n * Stores the list of focusable elements in the modal\n */\n @State() focusableElementsArray: HTMLElement[] = [];\n\n componentDidLoad() {\n this.modalRef = this.el.querySelector('.pds-modal__backdrop') as HTMLDialogElement;\n // Add keyboard event listener\n document.addEventListener('keydown', this.handleKeyDown);\n }\n\n disconnectedCallback() {\n // Clean up event listener\n document.removeEventListener('keydown', this.handleKeyDown);\n }\n\n @Watch('open')\n handleOpenChange(newValue: boolean) {\n if (newValue) {\n this.showModal();\n } else {\n this.hideModal();\n }\n }\n\n /**\n * Updates the list of focusable elements in the modal\n */\n private updateFocusableElements() {\n if (!this.modalRef) return;\n\n // Get all focusable elements within the modal\n const selector = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n 'pds-button:not([disabled])',\n 'pds-link:not([disabled])',\n 'pds-input:not([disabled])',\n 'pds-checkbox:not([disabled])',\n 'pds-radio:not([disabled])',\n 'pds-switch:not([disabled])',\n 'pds-select:not([disabled])',\n ].join(',');\n\n this.focusableElements = Array.from(\n this.modalRef.querySelectorAll(selector)\n ) as HTMLElement[];\n\n // Filter out elements with display: none or visibility: hidden\n this.focusableElements = this.focusableElements.filter(el => {\n const style = window.getComputedStyle(el);\n return style.display !== 'none' && style.visibility !== 'hidden';\n });\n }\n\n /**\n * Sets focus to the first focusable element in the modal\n */\n private setInitialFocus() {\n if (this.focusableElements.length === 0) return;\n\n // Focus the first focusable element\n const firstElement = this.focusableElements[0];\n\n // For web components, we need to ensure they're properly focused\n this.focusElement(firstElement);\n }\n\n /**\n * Helper method to focus an element, with special handling for web components\n */\n private focusElement(element: HTMLElement) {\n if (!element) return;\n\n try {\n // Try standard focus first\n element.focus();\n\n // Check if focus worked\n setTimeout(() => {\n if (document.activeElement !== element) {\n // For web components, try to find a focusable element inside\n if (element.shadowRoot) {\n const focusableInShadow = element.shadowRoot.querySelector(\n 'button, [tabindex], input, a[href]'\n ) as HTMLElement;\n\n if (focusableInShadow) {\n focusableInShadow.focus();\n }\n }\n }\n }, 0);\n } catch (error) {\n console.error('Error focusing element:', error);\n }\n }\n\n /**\n * Opens the modal\n */\n @Method()\n async showModal() {\n if (this.modalRef) {\n try {\n // Store the currently focused element to restore focus when modal closes\n this.previousActiveElement = document.activeElement as HTMLElement;\n\n // Use native dialog showModal method which makes the rest of the page inert\n this.modalRef.showModal();\n this.open = true;\n\n // Update focusable elements and set initial focus\n // Using a longer timeout to ensure all components are fully rendered\n setTimeout(() => {\n this.updateFocusableElements();\n this.setInitialFocus();\n this.pdsModalOpen.emit();\n }, 100);\n } catch (error) {\n console.error('Failed to show modal:', error);\n }\n }\n }\n\n /**\n * Closes the modal\n */\n @Method()\n async hideModal() {\n if (this.modalRef) {\n try {\n this.modalRef.close();\n this.open = false;\n\n // Restore focus to the element that was focused before the modal was opened\n if (this.previousActiveElement && typeof this.previousActiveElement.focus === 'function') {\n this.previousActiveElement.focus();\n }\n\n this.pdsModalClose.emit();\n } catch (error) {\n console.error('Failed to hide modal:', error);\n }\n }\n }\n\n private handleBackdropClick = (e: MouseEvent) => {\n if (!this.backdropDismiss || !this.open) return;\n\n if ((e.target as HTMLElement).classList.contains('pds-modal__backdrop')) {\n e.stopPropagation();\n\n // Only close if this is the innermost modal\n if (this.isInnermostModal()) {\n this.hideModal();\n }\n }\n };\n\n /**\n * Gets the z-index of a modal's backdrop element\n */\n private getBackdropZIndex(modal: Element): number {\n const backdrop = modal.querySelector('.pds-modal__backdrop');\n return backdrop ? parseInt(getComputedStyle(backdrop).zIndex, 10) : -1;\n }\n\n /**\n * Checks if this modal is the innermost (highest z-index) modal\n */\n private isInnermostModal(): boolean {\n // Find all open modals\n const openModals = Array.from(document.querySelectorAll('pds-modal')).filter(\n modal => modal.open\n );\n\n if (openModals.length === 0) return false;\n\n // Get this modal's backdrop element\n const thisBackdrop = this.el.querySelector('.pds-modal__backdrop');\n if (!thisBackdrop) return false;\n\n // Get computed z-index of all open modal backdrops\n const modalZIndexes = openModals.map(modal => this.getBackdropZIndex(modal));\n\n // Get the highest z-index\n const maxZIndex = Math.max(...modalZIndexes);\n\n // Check if this modal's backdrop has the highest z-index\n const thisZIndex = this.getBackdropZIndex(this.el);\n return thisZIndex === maxZIndex;\n }\n\n private handleKeyDown = (e: KeyboardEvent) => {\n // If the modal is not open, don't handle any keyboard events\n if (!this.open) return;\n\n // Handle Escape key to close the modal\n if (e.key === 'Escape') {\n // Only close if this is the innermost modal\n if (this.isInnermostModal()) {\n e.preventDefault();\n this.hideModal();\n }\n return;\n }\n\n // Handle Tab key for focus trapping\n if (e.key === 'Tab') {\n // If there are no focusable elements, do nothing\n if (this.focusableElements.length === 0) return;\n\n // Get the first and last focusable elements\n const firstFocusableElement = this.focusableElements[0];\n const lastFocusableElement = this.focusableElements[this.focusableElements.length - 1];\n\n // Get the current active element\n const activeElement = document.activeElement;\n\n // Check if we need to wrap focus\n const isFirstElement = activeElement === firstFocusableElement ||\n firstFocusableElement.contains(activeElement as Node);\n\n const isLastElement = activeElement === lastFocusableElement ||\n lastFocusableElement.contains(activeElement as Node);\n\n // If shift + tab is pressed and focus is on the first element, move to the last element\n if (e.shiftKey && isFirstElement) {\n e.preventDefault();\n this.focusElement(lastFocusableElement);\n }\n // If tab is pressed and focus is on the last element, move to the first element\n else if (!e.shiftKey && isLastElement) {\n e.preventDefault();\n this.focusElement(firstFocusableElement);\n }\n }\n };\n\n render() {\n return (\n <dialog\n class={{\n 'pds-modal__backdrop': true,\n 'open': this.open\n }}\n aria-modal=\"true\"\n aria-labelledby={`${this.componentId}-heading`}\n onClick={this.handleBackdropClick}\n >\n <div\n class={`pds-modal pds-modal--${this.size} pds-modal--scrollable`}\n >\n <slot></slot>\n </div>\n </dialog>\n );\n }\n}\n"]}
1
+ {"version":3,"file":"pds-modal.js","sourceRoot":"","sources":["../../../src/components/pds-modal/pds-modal.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Component, Element, Event, EventEmitter, h, Method, Prop, State, Watch } from '@stencil/core';\n\n@Component({\n tag: 'pds-modal',\n styleUrl: 'pds-modal.scss',\n shadow: false\n})\nexport class PdsModal {\n private modalRef: HTMLDialogElement;\n private previousActiveElement: HTMLElement;\n private focusableElements: HTMLElement[] = [];\n\n @Element() el: HTMLPdsModalElement;\n\n /**\n * Whether the modal can be dismissed by clicking the backdrop\n * @default true\n */\n @Prop() backdropDismiss = true;\n\n /**\n * A unique identifier used for the underlying component `id` attribute.\n */\n @Prop() componentId: string;\n\n /**\n * Whether the modal is open\n * @default false\n */\n @Prop({ mutable: true }) open = false;\n\n /**\n * The size of the modal\n * @default 'md'\n */\n @Prop() size: 'sm' | 'md' | 'lg' | 'fullscreen' = 'md';\n\n /**\n * Whether the modal content should be scrollable\n * @default true\n */\n @Prop() scrollable = true;\n\n /**\n * Emitted when the modal is opened\n */\n @Event() pdsModalOpen: EventEmitter<void>;\n\n /**\n * Emitted when the modal is closed\n */\n @Event() pdsModalClose: EventEmitter<void>;\n\n /**\n * Stores the list of focusable elements in the modal\n */\n @State() focusableElementsArray: HTMLElement[] = [];\n\n componentDidLoad() {\n this.modalRef = this.el.querySelector('.pds-modal__backdrop') as HTMLDialogElement;\n // Add keyboard event listener\n document.addEventListener('keydown', this.handleKeyDown);\n }\n\n disconnectedCallback() {\n // Clean up event listener\n document.removeEventListener('keydown', this.handleKeyDown);\n }\n\n @Watch('open')\n handleOpenChange(newValue: boolean) {\n if (newValue) {\n this.showModal();\n } else {\n this.hideModal();\n }\n }\n\n /**\n * Updates the list of focusable elements in the modal\n */\n private updateFocusableElements() {\n if (!this.modalRef) return;\n\n // Get all focusable elements within the modal\n const selector = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n 'pds-button:not([disabled])',\n 'pds-link:not([disabled])',\n 'pds-input:not([disabled])',\n 'pds-checkbox:not([disabled])',\n 'pds-radio:not([disabled])',\n 'pds-switch:not([disabled])',\n 'pds-select:not([disabled])',\n ].join(',');\n\n this.focusableElements = Array.from(\n this.modalRef.querySelectorAll(selector)\n ) as HTMLElement[];\n\n // Filter out elements with display: none or visibility: hidden\n this.focusableElements = this.focusableElements.filter(el => {\n const style = window.getComputedStyle(el);\n return style.display !== 'none' && style.visibility !== 'hidden';\n });\n }\n\n /**\n * Sets focus to the first focusable element in the modal\n */\n private setInitialFocus() {\n if (this.focusableElements.length === 0) return;\n\n // Focus the first focusable element\n const firstElement = this.focusableElements[0];\n\n // For web components, we need to ensure they're properly focused\n this.focusElement(firstElement);\n }\n\n /**\n * Helper method to focus an element, with special handling for web components\n */\n private focusElement(element: HTMLElement) {\n if (!element) return;\n\n try {\n // Try standard focus first\n element.focus();\n\n // Check if focus worked\n setTimeout(() => {\n if (document.activeElement !== element) {\n // For web components, try to find a focusable element inside\n if (element.shadowRoot) {\n const focusableInShadow = element.shadowRoot.querySelector(\n 'button, [tabindex], input, a[href]'\n ) as HTMLElement;\n\n if (focusableInShadow) {\n focusableInShadow.focus();\n }\n }\n }\n }, 0);\n } catch (error) {\n console.error('Error focusing element:', error);\n }\n }\n\n /**\n * Opens the modal\n */\n @Method()\n async showModal() {\n if (this.modalRef) {\n try {\n // Store the currently focused element to restore focus when modal closes\n this.previousActiveElement = document.activeElement as HTMLElement;\n\n // Use native dialog showModal method which makes the rest of the page inert\n this.modalRef.showModal();\n this.open = true;\n\n // Update focusable elements and set initial focus\n // Using a longer timeout to ensure all components are fully rendered\n setTimeout(() => {\n this.updateFocusableElements();\n this.setInitialFocus();\n this.pdsModalOpen.emit();\n }, 100);\n } catch (error) {\n console.error('Failed to show modal:', error);\n }\n }\n }\n\n /**\n * Closes the modal\n */\n @Method()\n async hideModal() {\n if (this.modalRef) {\n try {\n this.modalRef.close();\n this.open = false;\n\n // Restore focus to the element that was focused before the modal was opened\n if (this.previousActiveElement && typeof this.previousActiveElement.focus === 'function') {\n this.previousActiveElement.focus();\n }\n\n this.pdsModalClose.emit();\n } catch (error) {\n console.error('Failed to hide modal:', error);\n }\n }\n }\n\n private handleBackdropClick = (e: MouseEvent) => {\n if (!this.backdropDismiss || !this.open) return;\n\n if ((e.target as HTMLElement).classList.contains('pds-modal__backdrop')) {\n e.stopPropagation();\n\n // Only close if this is the innermost modal\n if (this.isInnermostModal()) {\n this.hideModal();\n }\n }\n };\n\n /**\n * Gets the z-index of a modal's backdrop element\n */\n private getBackdropZIndex(modal: Element): number {\n const backdrop = modal.querySelector('.pds-modal__backdrop');\n return backdrop ? parseInt(getComputedStyle(backdrop).zIndex, 10) : -1;\n }\n\n /**\n * Checks if this modal is the innermost (highest z-index) modal\n */\n private isInnermostModal(): boolean {\n // Find all open modals\n const openModals = Array.from(document.querySelectorAll('pds-modal')).filter(\n modal => modal.open\n );\n\n if (openModals.length === 0) return false;\n\n // Get this modal's backdrop element\n const thisBackdrop = this.el.querySelector('.pds-modal__backdrop');\n if (!thisBackdrop) return false;\n\n // Get computed z-index of all open modal backdrops\n const modalZIndexes = openModals.map(modal => this.getBackdropZIndex(modal));\n\n // Get the highest z-index\n const maxZIndex = Math.max(...modalZIndexes);\n\n // Check if this modal's backdrop has the highest z-index\n const thisZIndex = this.getBackdropZIndex(this.el);\n return thisZIndex === maxZIndex;\n }\n\n private handleKeyDown = (e: KeyboardEvent) => {\n // If the modal is not open, don't handle any keyboard events\n if (!this.open) return;\n\n // Handle Escape key to close the modal\n if (e.key === 'Escape') {\n // Only close if this is the innermost modal\n if (this.isInnermostModal()) {\n e.preventDefault();\n this.hideModal();\n }\n return;\n }\n\n // Handle Tab key for focus trapping\n if (e.key === 'Tab') {\n // If there are no focusable elements, do nothing\n if (this.focusableElements.length === 0) return;\n\n // Get the first and last focusable elements\n const firstFocusableElement = this.focusableElements[0];\n const lastFocusableElement = this.focusableElements[this.focusableElements.length - 1];\n\n // Get the current active element\n const activeElement = document.activeElement;\n\n // Check if we need to wrap focus\n const isFirstElement = activeElement === firstFocusableElement ||\n firstFocusableElement.contains(activeElement as Node);\n\n const isLastElement = activeElement === lastFocusableElement ||\n lastFocusableElement.contains(activeElement as Node);\n\n // If shift + tab is pressed and focus is on the first element, move to the last element\n if (e.shiftKey && isFirstElement) {\n e.preventDefault();\n this.focusElement(lastFocusableElement);\n }\n // If tab is pressed and focus is on the last element, move to the first element\n else if (!e.shiftKey && isLastElement) {\n e.preventDefault();\n this.focusElement(firstFocusableElement);\n }\n }\n };\n\n render() {\n return (\n <dialog\n class={{\n 'pds-modal__backdrop': true,\n 'open': this.open\n }}\n aria-modal=\"true\"\n aria-labelledby={`${this.componentId}-heading`}\n onClick={this.handleBackdropClick}\n >\n <div\n class={{\n 'pds-modal': true,\n [`pds-modal--${this.size}`]: true,\n 'pds-modal--scrollable': this.scrollable\n }}\n >\n <slot></slot>\n </div>\n </dialog>\n );\n }\n}\n"]}
@@ -11,6 +11,7 @@ export default {
11
11
  backdropDismiss: true,
12
12
  componentId: 'modal-demo',
13
13
  open: false,
14
+ scrollable: true,
14
15
  size: 'md',
15
16
  },
16
17
  parameters: {
@@ -31,7 +32,9 @@ const BaseTemplate = (args) => html`
31
32
  component-id="${args.componentId}"
32
33
  size="${args.size}"
33
34
  ?backdrop-dismiss=${args.backdropDismiss}
35
+ scrollable="${args.scrollable}"
34
36
  ?open=${args.open}
37
+ key="${args.scrollable ? 'scrollable' : 'non-scrollable'}"
35
38
  >
36
39
  <pds-modal-header>
37
40
  <pds-box direction="column" fit padding="md">
@@ -413,3 +416,245 @@ Fullscreen.args = {
413
416
  componentId: 'fullscreen-modal',
414
417
  open: false,
415
418
  };
419
+
420
+ const NonScrollableTemplate = (args) => html`
421
+ <div style="padding: 1rem;">
422
+ <pds-button id="show-modal" onClick="document.querySelector('#${args.componentId}').open = true">
423
+ Open Non-Scrollable Modal
424
+ </pds-button>
425
+
426
+ <pds-modal
427
+ id="${args.componentId}"
428
+ component-id="${args.componentId}"
429
+ size="${args.size}"
430
+ ?backdrop-dismiss=${args.backdropDismiss}
431
+ scrollable="${args.scrollable}"
432
+ ?open=${args.open}
433
+ key="${args.scrollable ? 'scrollable' : 'non-scrollable'}"
434
+ >
435
+ <pds-modal-header>
436
+ <pds-box direction="column" fit padding="md">
437
+ <pds-box
438
+ align-items="center"
439
+ fit
440
+ justify-content="space-between"
441
+ >
442
+ <pds-text tag="h2" size="h3">Non-Scrollable Modal</pds-text>
443
+ <pds-button
444
+ class="pds-modal__close"
445
+ variant="unstyled"
446
+ icon-only="true"
447
+ onclick="document.querySelector('#${args.componentId}').open = false"
448
+ aria-label="Close modal"
449
+ part="close-button"
450
+ >
451
+ <pds-icon slot="start" name="remove" aria-hidden="true"></pds-icon>
452
+ </pds-button>
453
+ </pds-box>
454
+ <pds-box>
455
+ <pds-text tag="p" size="md">Content overflow is handled naturally</pds-text>
456
+ </pds-box>
457
+ </pds-box>
458
+ </pds-modal-header>
459
+ <pds-modal-content>
460
+ <pds-box direction="column" padding-inline-start="md" padding-inline-end="md">
461
+ <p>This modal has scrollable=false, so content will overflow naturally.</p>
462
+ <p>Use this when you want to control overflow behavior differently.</p>
463
+ <p>This is ideal for modals with fixed-height content or custom overflow handling.</p>
464
+ <p>This modal has scrollable=false, so content will overflow naturally.</p>
465
+ <p>Use this when you want to control overflow behavior differently.</p>
466
+ <p>This is ideal for modals with fixed-height content or custom overflow handling.</p>
467
+ <p>This modal has scrollable=false, so content will overflow naturally.</p>
468
+ <p>Use this when you want to control overflow behavior differently.</p>
469
+ <p>This modal has scrollable=false, so content will overflow naturally.</p>
470
+ <p>Use this when you want to control overflow behavior differently.</p>
471
+ <p>This is ideal for modals with fixed-height content or custom overflow handling.</p>
472
+ <p>This modal has scrollable=false, so content will overflow naturally.</p>
473
+ <p>Use this when you want to control overflow behavior differently.</p>
474
+ <p>This is ideal for modals with fixed-height content or custom overflow handling.</p>
475
+ <p>This modal has scrollable=false, so content will overflow naturally.</p>
476
+ <p>Use this when you want to control overflow behavior differently.</p>
477
+ <p>This is ideal for modals with fixed-height content or custom overflow handling.</p>
478
+ </pds-box>
479
+ </pds-modal-content>
480
+
481
+ <pds-modal-footer>
482
+ <pds-box
483
+ justify-content="space-between"
484
+ fit
485
+ padding="md"
486
+ >
487
+ <pds-button variant="unstyled" onclick="document.querySelector('#${args.componentId}').open = false">Close</pds-button>
488
+ <pds-box gap="sm" justify-content="end">
489
+ <pds-button
490
+ variant="secondary"
491
+ onclick="document.querySelector('#${args.componentId}').open = false"
492
+ >
493
+ Cancel
494
+ </pds-button>
495
+ <pds-button variant="primary">Confirm</pds-button>
496
+ </pds-box>
497
+ </pds-box>
498
+ </pds-modal-footer>
499
+ </pds-modal>
500
+ </div>
501
+ `;
502
+
503
+ export const NonScrollable = NonScrollableTemplate.bind({});
504
+ NonScrollable.args = {
505
+ size: 'md',
506
+ componentId: 'non-scrollable-modal',
507
+ scrollable: false,
508
+ open: false,
509
+ };
510
+
511
+ const CustomBordersTemplate = () => html`
512
+ <div style="padding: 1rem;">
513
+ <pds-box gap="sm" direction="column">
514
+ <pds-text tag="h3" size="h4">Border Examples</pds-text>
515
+ <pds-box gap="sm" wrap>
516
+ <pds-button id="show-modal-none" onClick="document.querySelector('#border-none-modal').open = true">
517
+ border="none"
518
+ </pds-button>
519
+ <pds-button id="show-modal-top" onClick="document.querySelector('#border-top-modal').open = true">
520
+ border="top"
521
+ </pds-button>
522
+ <pds-button id="show-modal-bottom" onClick="document.querySelector('#border-bottom-modal').open = true">
523
+ border="bottom"
524
+ </pds-button>
525
+ <pds-button id="show-modal-both" onClick="document.querySelector('#border-both-modal').open = true">
526
+ border="both"
527
+ </pds-button>
528
+ </pds-box>
529
+ </pds-box>
530
+
531
+ <!-- Border None Modal -->
532
+ <pds-modal id="border-none-modal" component-id="border-none-modal" size="sm">
533
+ <pds-modal-header>
534
+ <pds-box direction="column" fit padding="md">
535
+ <pds-box align-items="center" fit justify-content="space-between">
536
+ <pds-text tag="h2" size="h3">No Borders</pds-text>
537
+ <pds-button
538
+ class="pds-modal__close"
539
+ variant="unstyled"
540
+ icon-only="true"
541
+ onclick="document.querySelector('#border-none-modal').open = false"
542
+ aria-label="Close modal"
543
+ >
544
+ <pds-icon slot="start" name="remove" aria-hidden="true"></pds-icon>
545
+ </pds-button>
546
+ </pds-box>
547
+ </pds-box>
548
+ </pds-modal-header>
549
+ <pds-modal-content border="none">
550
+ <pds-box fit direction="column" padding-inline-start="md" padding-inline-end="md">
551
+ <p>This modal content has <code>border="none"</code></p>
552
+ <p>No borders will be shown regardless of content length or scroll position.</p>
553
+ </pds-box>
554
+ </pds-modal-content>
555
+ <pds-modal-footer>
556
+ <pds-box fit justify-content="end" padding="md" gap="sm">
557
+ <pds-button onclick="document.querySelector('#border-none-modal').open = false">Close</pds-button>
558
+ </pds-box>
559
+ </pds-modal-footer>
560
+ </pds-modal>
561
+
562
+ <!-- Border Top Modal -->
563
+ <pds-modal id="border-top-modal" component-id="border-top-modal" size="sm">
564
+ <pds-modal-header>
565
+ <pds-box direction="column" fit padding="md">
566
+ <pds-box align-items="center" fit justify-content="space-between">
567
+ <pds-text tag="h2" size="h3">Top Border Only</pds-text>
568
+ <pds-button
569
+ class="pds-modal__close"
570
+ variant="unstyled"
571
+ icon-only="true"
572
+ onclick="document.querySelector('#border-top-modal').open = false"
573
+ aria-label="Close modal"
574
+ >
575
+ <pds-icon slot="start" name="remove" aria-hidden="true"></pds-icon>
576
+ </pds-button>
577
+ </pds-box>
578
+ </pds-box>
579
+ </pds-modal-header>
580
+ <pds-modal-content border="top">
581
+ <pds-box fit direction="column" padding-inline-start="md" padding-inline-end="md">
582
+ <p>This modal content has <code>border="top"</code></p>
583
+ <p>Only the top border is visible, indicating there's content above.</p>
584
+ </pds-box>
585
+ </pds-modal-content>
586
+ <pds-modal-footer>
587
+ <pds-box fit justify-content="end" padding="md" gap="sm">
588
+ <pds-button onclick="document.querySelector('#border-top-modal').open = false">Close</pds-button>
589
+ </pds-box>
590
+ </pds-modal-footer>
591
+ </pds-modal>
592
+
593
+ <!-- Border Bottom Modal -->
594
+ <pds-modal id="border-bottom-modal" component-id="border-bottom-modal" size="sm">
595
+ <pds-modal-header>
596
+ <pds-box direction="column" fit padding="md">
597
+ <pds-box align-items="center" fit justify-content="space-between">
598
+ <pds-text tag="h2" size="h3">Bottom Border Only</pds-text>
599
+ <pds-button
600
+ class="pds-modal__close"
601
+ variant="unstyled"
602
+ icon-only="true"
603
+ onclick="document.querySelector('#border-bottom-modal').open = false"
604
+ aria-label="Close modal"
605
+ >
606
+ <pds-icon slot="start" name="remove" aria-hidden="true"></pds-icon>
607
+ </pds-button>
608
+ </pds-box>
609
+ </pds-box>
610
+ </pds-modal-header>
611
+ <pds-modal-content border="bottom">
612
+ <pds-box fit direction="column" padding-inline-start="md" padding-inline-end="md">
613
+ <p>This modal content has <code>border="bottom"</code></p>
614
+ <p>Only the bottom border is visible, indicating there's content below.</p>
615
+ </pds-box>
616
+ </pds-modal-content>
617
+ <pds-modal-footer>
618
+ <pds-box fit justify-content="end" padding="md" gap="sm">
619
+ <pds-button onclick="document.querySelector('#border-bottom-modal').open = false">Close</pds-button>
620
+ </pds-box>
621
+ </pds-modal-footer>
622
+ </pds-modal>
623
+
624
+ <!-- Border Both Modal -->
625
+ <pds-modal id="border-both-modal" component-id="border-both-modal" size="sm">
626
+ <pds-modal-header>
627
+ <pds-box direction="column" fit padding="md">
628
+ <pds-box align-items="center" fit justify-content="space-between">
629
+ <pds-text tag="h2" size="h3">Both Borders</pds-text>
630
+ <pds-button
631
+ class="pds-modal__close"
632
+ variant="unstyled"
633
+ icon-only="true"
634
+ onclick="document.querySelector('#border-both-modal').open = false"
635
+ aria-label="Close modal"
636
+ >
637
+ <pds-icon slot="start" name="remove" aria-hidden="true"></pds-icon>
638
+ </pds-button>
639
+ </pds-box>
640
+ </pds-box>
641
+ </pds-modal-header>
642
+ <pds-modal-content border="both">
643
+ <pds-box fit direction="column" padding-inline-start="md" padding-inline-end="md">
644
+ <p>This modal content has <code>border="both"</code></p>
645
+ <p>Both top and bottom borders are visible, indicating content above and below.</p>
646
+ </pds-box>
647
+ </pds-modal-content>
648
+ <pds-modal-footer>
649
+ <pds-box fit justify-content="end" padding="md" gap="sm">
650
+ <pds-button onclick="document.querySelector('#border-both-modal').open = false">Close</pds-button>
651
+ </pds-box>
652
+ </pds-modal-footer>
653
+ </pds-modal>
654
+ </div>
655
+ `;
656
+
657
+ export const CustomBorders = CustomBordersTemplate.bind({});
658
+ CustomBorders.args = {
659
+ componentId: 'custom-borders-modal',
660
+ };
@@ -9,7 +9,11 @@ export class MockPdsModal {
9
9
  * The size of the modal
10
10
  */
11
11
  this.size = 'md';
12
- // Modal content is always scrollable by default
12
+ /**
13
+ * Whether the modal content should be scrollable
14
+ * @default true
15
+ */
16
+ this.scrollable = true;
13
17
  /**
14
18
  * Whether the modal can be dismissed by clicking the backdrop
15
19
  */
@@ -63,13 +67,13 @@ export class MockPdsModal {
63
67
  const modalClasses = {
64
68
  'pds-modal': true,
65
69
  [`pds-modal--${this.size}`]: true,
66
- 'pds-modal--scrollable': true, // Always scrollable by default
70
+ 'pds-modal--scrollable': this.scrollable
67
71
  };
68
72
  const backdropClasses = {
69
73
  'pds-modal__backdrop': true,
70
74
  'open': this.open,
71
75
  };
72
- return (h("div", { key: '1add4d6197e52590f97de144745dc7e13b79fc2f', class: backdropClasses }, h("div", { key: 'f9e0dcf40e353041a4b135eb687a89b99352a86e', class: modalClasses, role: "dialog", "aria-modal": "true", "aria-labelledby": this.componentId ? `${this.componentId}-heading` : null }, h("div", { key: 'a0eff71ed114a48f588f2eab147a12ecd513ef23', class: "pds-modal__header" }, h("slot", { key: '0b58df0cc5241ffffa459f07f913c490fff3adce', name: "header" })), h("div", { key: '41e5ee8b58bb60bcc8f1aa72a7b24839a68a2e9f', class: "pds-modal-content" }, h("slot", { key: '4c9a073f2d6ddcd7942ec1f1839886c0868c15a3' })), h("div", { key: '89d542843d68c860fe2526bbc05fac4893c3d616', class: "pds-modal__footer" }, h("slot", { key: '1d5720bc3a86d97309c015d3c98ee25f85ef7c34', name: "footer" })))));
76
+ return (h("div", { key: '0fccdb57d79f636ec3b5782943ddd76fbc9ce171', class: backdropClasses }, h("div", { key: '341963284bfbcd1f99df53e346ed3257b00dbeb2', class: modalClasses, role: "dialog", "aria-modal": "true", "aria-labelledby": this.componentId ? `${this.componentId}-heading` : null }, h("div", { key: '8ff526d8c2d8c0696f9fff107928cf9cb59dbdad', class: "pds-modal__header" }, h("slot", { key: '8746d9cb026e70b293be00f71ffa2939d80947ee', name: "header" })), h("div", { key: '48b98256d3114016de8279012e2c496726f766ba', class: "pds-modal-content" }, h("slot", { key: '49ff7217a2c8d59c56751c850c4372329e263e7b' })), h("div", { key: 'd46d11d288bff5a4539d0cb4b2c7ea2111687ebf', class: "pds-modal__footer" }, h("slot", { key: '549d4f33a78db562b5afce77cc108cc22f474a0d', name: "footer" })))));
73
77
  }
74
78
  static get is() { return "mock-pds-modal"; }
75
79
  static get originalStyleUrls() {
@@ -123,6 +127,29 @@ export class MockPdsModal {
123
127
  "reflect": false,
124
128
  "defaultValue": "'md'"
125
129
  },
130
+ "scrollable": {
131
+ "type": "boolean",
132
+ "mutable": false,
133
+ "complexType": {
134
+ "original": "boolean",
135
+ "resolved": "boolean",
136
+ "references": {}
137
+ },
138
+ "required": false,
139
+ "optional": false,
140
+ "docs": {
141
+ "tags": [{
142
+ "name": "default",
143
+ "text": "true"
144
+ }],
145
+ "text": "Whether the modal content should be scrollable"
146
+ },
147
+ "getter": false,
148
+ "setter": false,
149
+ "attribute": "scrollable",
150
+ "reflect": false,
151
+ "defaultValue": "true"
152
+ },
126
153
  "backdropDismiss": {
127
154
  "type": "boolean",
128
155
  "mutable": false,
@@ -1 +1 @@
1
- {"version":3,"file":"mock-pds-modal.js","sourceRoot":"","sources":["../../../../src/components/pds-modal/test/mock-pds-modal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAgB,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;AAWzF;;;GAGG;AAMH,MAAM,OAAO,YAAY;IALzB;QAaE;;WAEG;QACK,SAAI,GAAsC,IAAI,CAAC;QAEvD,gDAAgD;QAEhD;;WAEG;QACK,oBAAe,GAAG,IAAI,CAAC;QAE/B,+FAA+F;QAE/F;;WAEG;QACsB,SAAI,GAAG,KAAK,CAAC;KAkGvC;IA/EC;;OAEG;IAEH,KAAK,CAAC,SAAS;QACb,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;IAC3B,CAAC;IAED;;OAEG;IAEH,KAAK,CAAC,SAAS;QACb,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;QAClB,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,iEAAiE;IACjE,mBAAmB,CAAC,KAAiB;QACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;QAC/D,2EAA2E;QAC3E,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,eAAe,KAAK,IAAI,EAAE,CAAC;YAC/D,IAAI,CAAC,qBAAqB,CAAC,IAAI,EAAE,CAAC;YAClC,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,iEAAiE;IACjE,aAAa,CAAC,KAAoB;QAChC,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACjD,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAED,gBAAgB;QACd,0DAA0D;IAC5D,CAAC;IAED,MAAM;QACJ,MAAM,YAAY,GAAG;YACnB,WAAW,EAAE,IAAI;YACjB,CAAC,cAAc,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI;YACjC,uBAAuB,EAAE,IAAI,EAAE,+BAA+B;SAC/D,CAAC;QAEF,MAAM,eAAe,GAAG;YACtB,qBAAqB,EAAE,IAAI;YAC3B,MAAM,EAAE,IAAI,CAAC,IAAI;SAClB,CAAC;QAEF,OAAO,CACL,4DAAK,KAAK,EAAE,eAAe;YACzB,4DACE,KAAK,EAAE,YAAY,EACnB,IAAI,EAAC,QAAQ,gBACF,MAAM,qBACA,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,WAAW,UAAU,CAAC,CAAC,CAAC,IAAI;gBAExE,4DAAK,KAAK,EAAC,mBAAmB;oBAC5B,6DAAM,IAAI,EAAC,QAAQ,GAAQ,CACvB;gBACN,4DAAK,KAAK,EAAC,mBAAmB;oBAC5B,8DAAa,CACT;gBACN,4DAAK,KAAK,EAAC,mBAAmB;oBAC5B,6DAAM,IAAI,EAAC,QAAQ,GAAQ,CACvB,CACF,CACF,CACP,CAAC;IACJ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CACF","sourcesContent":["import { Component, Prop, Event, EventEmitter, Method, Element, h } from '@stencil/core';\n\n// Define the element interface for the mock component\ninterface HTMLMockPdsModalElement extends HTMLElement {\n open: boolean;\n showModal: () => Promise<void>;\n hideModal: () => Promise<void>;\n handleBackdropClick: (event: MouseEvent) => void;\n handleKeyDown: (event: KeyboardEvent) => void;\n}\n\n/**\n * Mock PdsModal component for testing purposes\n * This component mimics the real PdsModal but without using the Popover API\n */\n@Component({\n tag: 'mock-pds-modal',\n styleUrl: '../pds-modal.scss',\n shadow: false,\n})\nexport class MockPdsModal {\n @Element() el!: HTMLMockPdsModalElement;\n\n /**\n * The ID of the modal component\n */\n @Prop() componentId?: string;\n\n /**\n * The size of the modal\n */\n @Prop() size: 'sm' | 'md' | 'lg' | 'fullscreen' = 'md';\n\n // Modal content is always scrollable by default\n\n /**\n * Whether the modal can be dismissed by clicking the backdrop\n */\n @Prop() backdropDismiss = true;\n\n // Native dialog element always closes on Escape key press, so no closeOnEsc property is needed\n\n /**\n * Whether the modal is open\n */\n @Prop({ mutable: true }) open = false;\n\n // No need for modalRef in the mock implementation\n\n /**\n * Event emitted when the modal is opened\n */\n @Event() pdsModalOpen: EventEmitter;\n\n /**\n * Event emitted when the modal is closed\n */\n @Event() pdsModalClose: EventEmitter;\n\n /**\n * Event emitted when the backdrop is clicked\n */\n @Event() pdsModalBackdropClick: EventEmitter;\n\n /**\n * Shows the modal\n */\n @Method()\n async showModal() {\n this.open = true;\n this.pdsModalOpen.emit();\n }\n\n /**\n * Hides the modal\n */\n @Method()\n async hideModal() {\n this.open = false;\n this.pdsModalClose.emit();\n }\n\n /**\n * Listen for click events on the backdrop\n */\n // Using direct method instead of @Listen to avoid ESLint warning\n handleBackdropClick(event: MouseEvent) {\n const backdrop = this.el.querySelector('.pds-modal__backdrop');\n // Check if the click was directly on the backdrop (not on a child element)\n if (event.target === backdrop && this.backdropDismiss === true) {\n this.pdsModalBackdropClick.emit();\n this.hideModal();\n }\n }\n\n /**\n * Listen for keydown events to handle Escape key\n * Native dialog element always closes on Escape key press\n */\n // Using direct method instead of @Listen to avoid ESLint warning\n handleKeyDown(event: KeyboardEvent) {\n if (event.key === 'Escape' && this.open === true) {\n this.hideModal();\n }\n }\n\n componentDidLoad() {\n // No need to do anything in componentDidLoad for the mock\n }\n\n render() {\n const modalClasses = {\n 'pds-modal': true,\n [`pds-modal--${this.size}`]: true,\n 'pds-modal--scrollable': true, // Always scrollable by default\n };\n\n const backdropClasses = {\n 'pds-modal__backdrop': true,\n 'open': this.open,\n };\n\n return (\n <div class={backdropClasses}>\n <div\n class={modalClasses}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={this.componentId ? `${this.componentId}-heading` : null}\n >\n <div class=\"pds-modal__header\">\n <slot name=\"header\"></slot>\n </div>\n <div class=\"pds-modal-content\">\n <slot></slot>\n </div>\n <div class=\"pds-modal__footer\">\n <slot name=\"footer\"></slot>\n </div>\n </div>\n </div>\n );\n }\n}\n"]}
1
+ {"version":3,"file":"mock-pds-modal.js","sourceRoot":"","sources":["../../../../src/components/pds-modal/test/mock-pds-modal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAgB,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,eAAe,CAAC;AAWzF;;;GAGG;AAMH,MAAM,OAAO,YAAY;IALzB;QAaE;;WAEG;QACK,SAAI,GAAsC,IAAI,CAAC;QAEvD;;;WAGG;QACK,eAAU,GAAG,IAAI,CAAC;QAE1B;;WAEG;QACK,oBAAe,GAAG,IAAI,CAAC;QAE/B,+FAA+F;QAE/F;;WAEG;QACsB,SAAI,GAAG,KAAK,CAAC;KAkGvC;IA/EC;;OAEG;IAEH,KAAK,CAAC,SAAS;QACb,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;IAC3B,CAAC;IAED;;OAEG;IAEH,KAAK,CAAC,SAAS;QACb,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;QAClB,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,iEAAiE;IACjE,mBAAmB,CAAC,KAAiB;QACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;QAC/D,2EAA2E;QAC3E,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,eAAe,KAAK,IAAI,EAAE,CAAC;YAC/D,IAAI,CAAC,qBAAqB,CAAC,IAAI,EAAE,CAAC;YAClC,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,iEAAiE;IACjE,aAAa,CAAC,KAAoB;QAChC,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACjD,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAED,gBAAgB;QACd,0DAA0D;IAC5D,CAAC;IAED,MAAM;QACJ,MAAM,YAAY,GAAG;YACnB,WAAW,EAAE,IAAI;YACjB,CAAC,cAAc,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI;YACjC,uBAAuB,EAAE,IAAI,CAAC,UAAU;SACzC,CAAC;QAEF,MAAM,eAAe,GAAG;YACtB,qBAAqB,EAAE,IAAI;YAC3B,MAAM,EAAE,IAAI,CAAC,IAAI;SAClB,CAAC;QAEF,OAAO,CACL,4DAAK,KAAK,EAAE,eAAe;YACzB,4DACE,KAAK,EAAE,YAAY,EACnB,IAAI,EAAC,QAAQ,gBACF,MAAM,qBACA,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,WAAW,UAAU,CAAC,CAAC,CAAC,IAAI;gBAExE,4DAAK,KAAK,EAAC,mBAAmB;oBAC5B,6DAAM,IAAI,EAAC,QAAQ,GAAQ,CACvB;gBACN,4DAAK,KAAK,EAAC,mBAAmB;oBAC5B,8DAAa,CACT;gBACN,4DAAK,KAAK,EAAC,mBAAmB;oBAC5B,6DAAM,IAAI,EAAC,QAAQ,GAAQ,CACvB,CACF,CACF,CACP,CAAC;IACJ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CACF","sourcesContent":["import { Component, Prop, Event, EventEmitter, Method, Element, h } from '@stencil/core';\n\n// Define the element interface for the mock component\ninterface HTMLMockPdsModalElement extends HTMLElement {\n open: boolean;\n showModal: () => Promise<void>;\n hideModal: () => Promise<void>;\n handleBackdropClick: (event: MouseEvent) => void;\n handleKeyDown: (event: KeyboardEvent) => void;\n}\n\n/**\n * Mock PdsModal component for testing purposes\n * This component mimics the real PdsModal but without using the Popover API\n */\n@Component({\n tag: 'mock-pds-modal',\n styleUrl: '../pds-modal.scss',\n shadow: false,\n})\nexport class MockPdsModal {\n @Element() el!: HTMLMockPdsModalElement;\n\n /**\n * The ID of the modal component\n */\n @Prop() componentId?: string;\n\n /**\n * The size of the modal\n */\n @Prop() size: 'sm' | 'md' | 'lg' | 'fullscreen' = 'md';\n\n /**\n * Whether the modal content should be scrollable\n * @default true\n */\n @Prop() scrollable = true;\n\n /**\n * Whether the modal can be dismissed by clicking the backdrop\n */\n @Prop() backdropDismiss = true;\n\n // Native dialog element always closes on Escape key press, so no closeOnEsc property is needed\n\n /**\n * Whether the modal is open\n */\n @Prop({ mutable: true }) open = false;\n\n // No need for modalRef in the mock implementation\n\n /**\n * Event emitted when the modal is opened\n */\n @Event() pdsModalOpen: EventEmitter;\n\n /**\n * Event emitted when the modal is closed\n */\n @Event() pdsModalClose: EventEmitter;\n\n /**\n * Event emitted when the backdrop is clicked\n */\n @Event() pdsModalBackdropClick: EventEmitter;\n\n /**\n * Shows the modal\n */\n @Method()\n async showModal() {\n this.open = true;\n this.pdsModalOpen.emit();\n }\n\n /**\n * Hides the modal\n */\n @Method()\n async hideModal() {\n this.open = false;\n this.pdsModalClose.emit();\n }\n\n /**\n * Listen for click events on the backdrop\n */\n // Using direct method instead of @Listen to avoid ESLint warning\n handleBackdropClick(event: MouseEvent) {\n const backdrop = this.el.querySelector('.pds-modal__backdrop');\n // Check if the click was directly on the backdrop (not on a child element)\n if (event.target === backdrop && this.backdropDismiss === true) {\n this.pdsModalBackdropClick.emit();\n this.hideModal();\n }\n }\n\n /**\n * Listen for keydown events to handle Escape key\n * Native dialog element always closes on Escape key press\n */\n // Using direct method instead of @Listen to avoid ESLint warning\n handleKeyDown(event: KeyboardEvent) {\n if (event.key === 'Escape' && this.open === true) {\n this.hideModal();\n }\n }\n\n componentDidLoad() {\n // No need to do anything in componentDidLoad for the mock\n }\n\n render() {\n const modalClasses = {\n 'pds-modal': true,\n [`pds-modal--${this.size}`]: true,\n 'pds-modal--scrollable': this.scrollable\n };\n\n const backdropClasses = {\n 'pds-modal__backdrop': true,\n 'open': this.open,\n };\n\n return (\n <div class={backdropClasses}>\n <div\n class={modalClasses}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={this.componentId ? `${this.componentId}-heading` : null}\n >\n <div class=\"pds-modal__header\">\n <slot name=\"header\"></slot>\n </div>\n <div class=\"pds-modal-content\">\n <slot></slot>\n </div>\n <div class=\"pds-modal__footer\">\n <slot name=\"footer\"></slot>\n </div>\n </div>\n </div>\n );\n }\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  :host {
2
- display: inline;
2
+ display: inline-block;
3
+ width: 100%;
3
4
  }
4
5
 
5
6
  :host([aria-readonly=true]) textarea {
@@ -34,13 +35,42 @@ label {
34
35
  margin-block-end: 0;
35
36
  }
36
37
 
38
+ .pds-textarea__helper-message {
39
+ color: var(--pine-color-text-message);
40
+ font: var(--pine-typography-body-sm-medium);
41
+ margin-block-start: var(--pine-dimension-2xs);
42
+ }
43
+
44
+ .pds-textarea__error-message {
45
+ align-items: center;
46
+ color: var(--pine-color-text-message-danger);
47
+ display: flex;
48
+ font: var(--pine-typography-body-sm-medium);
49
+ gap: var(--pine-dimension-2xs);
50
+ margin-block-start: var(--pine-dimension-2xs);
51
+ }
52
+
53
+ .pds-textarea__field-wrapper {
54
+ display: inline-block;
55
+ position: relative;
56
+ width: 100%;
57
+ }
58
+
37
59
  .pds-textarea__field {
38
60
  background-color: var(--pine-color-background-container);
39
61
  border: var(--pine-border);
40
62
  border-radius: calc(var(--pine-dimension-xs) * 1.25);
63
+ box-sizing: border-box;
41
64
  font: var(--pine-typography-body);
42
65
  letter-spacing: var(--pine-letter-spacing);
66
+ min-height: calc(var(--pine-dimension-xl) * 2);
67
+ min-width: calc(var(--pine-dimension-xl) * 2);
43
68
  padding: var(--pine-dimension-xs) var(--pine-dimension-sm);
69
+ resize: both;
70
+ width: 100%;
71
+ }
72
+ :host([max-length]) .pds-textarea__field {
73
+ padding-bottom: calc(var(--pine-dimension-xs) + var(--pine-dimension-md));
44
74
  }
45
75
  .pds-textarea__field:hover:not(:disabled, .is-invalid) {
46
76
  border: var(--pine-border-hover);
@@ -65,18 +95,28 @@ label {
65
95
  outline-color: var(--pine-color-focus-ring-danger);
66
96
  }
67
97
 
68
- .pds-textarea__error-message,
69
- .pds-textarea__helper-message {
70
- color: var(--pine-color-text-message);
98
+ .pds-textarea__character-counter {
99
+ background: color-mix(in srgb, var(--pine-color-background-container) 80%, transparent);
100
+ border-radius: calc(var(--pine-dimension-2xs) * 0.5);
101
+ color: var(--pine-color-text-readonly);
71
102
  font: var(--pine-typography-body-sm-medium);
72
- margin-block-start: var(--pine-dimension-xs);
103
+ padding: calc(var(--pine-dimension-2xs) * 0.5) var(--pine-dimension-2xs);
104
+ pointer-events: none;
105
+ position: absolute;
106
+ user-select: none;
107
+ white-space: nowrap;
108
+ z-index: 2;
73
109
  }
74
-
75
- .pds-textarea__error-message {
76
- align-items: center;
77
- color: var(--pine-color-text-message-danger);
78
- display: flex;
79
- gap: var(--pine-dimension-2xs);
110
+ :host([aria-disabled=true]) .pds-textarea__character-counter {
111
+ background-color: var(--pine-color-background-container-disabled);
112
+ color: var(--pine-color-text-disabled);
113
+ }
114
+ :host([aria-readonly=true]) .pds-textarea__character-counter {
115
+ background-color: var(--pine-color-background-container-disabled);
116
+ color: var(--pine-color-text-readonly);
117
+ }
118
+ .pds-textarea__field.is-invalid ~ .pds-textarea__character-counter {
119
+ background-color: var(--pine-input-color-background-danger);
80
120
  }
81
121
 
82
122
  .visually-hidden {