@pine-ds/core 4.7.1 → 4.8.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 (233) hide show
  1. package/components/pds-avatar.js +1 -1
  2. package/components/pds-button2.js +1 -1
  3. package/components/pds-checkbox2.js +1 -1
  4. package/components/pds-chip2.js +1 -1
  5. package/components/pds-combobox.js +1 -1
  6. package/components/pds-drawer.js +1 -1
  7. package/components/pds-dropdown-menu-item.js +1 -1
  8. package/components/pds-dropdown-menu.js +1 -1
  9. package/components/pds-modal-content.js +1 -1
  10. package/components/pds-modal2.js +1 -1
  11. package/components/pds-multiselect.js +1 -1
  12. package/components/pds-popover.js +1 -1
  13. package/components/pds-radio-group.js +1 -1
  14. package/components/pds-radio.js +1 -1
  15. package/components/pds-switch.js +1 -1
  16. package/components/pds-table-head-cell2.js +1 -1
  17. package/components/pds-textarea.js +1 -1
  18. package/components/pds-toast.js +1 -1
  19. package/components/truncation-tooltip.js +1 -1
  20. package/dist/cjs/{index-5Azb8EQG.js → index-Do70atou.js} +1 -1
  21. package/dist/cjs/loader.cjs.js +1 -1
  22. package/dist/cjs/pds-accordion.cjs.entry.js +1 -1
  23. package/dist/cjs/pds-avatar.cjs.entry.js +3 -3
  24. package/dist/cjs/pds-button.cjs.entry.js +24 -21
  25. package/dist/cjs/pds-checkbox.cjs.entry.js +2 -2
  26. package/dist/cjs/pds-chip.cjs.entry.js +4 -4
  27. package/dist/cjs/pds-combobox.cjs.entry.js +4 -4
  28. package/dist/cjs/pds-copytext.cjs.entry.js +2 -2
  29. package/dist/cjs/pds-datepicker.cjs.entry.js +1 -1
  30. package/dist/cjs/pds-drawer.cjs.entry.js +1 -1
  31. package/dist/cjs/pds-dropdown-menu-item.cjs.entry.js +31 -2
  32. package/dist/cjs/pds-dropdown-menu.cjs.entry.js +4 -4
  33. package/dist/cjs/pds-filter.cjs.entry.js +1 -1
  34. package/dist/cjs/pds-input.cjs.entry.js +1 -1
  35. package/dist/cjs/pds-link.cjs.entry.js +1 -1
  36. package/dist/cjs/pds-modal-content.cjs.entry.js +32 -30
  37. package/dist/cjs/pds-modal.cjs.entry.js +4 -4
  38. package/dist/cjs/pds-multiselect.cjs.entry.js +6 -6
  39. package/dist/cjs/pds-popover.cjs.entry.js +187 -11
  40. package/dist/cjs/pds-radio-group.cjs.entry.js +2 -2
  41. package/dist/cjs/pds-radio.cjs.entry.js +3 -3
  42. package/dist/cjs/pds-select.cjs.entry.js +1 -1
  43. package/dist/cjs/pds-sortable-item.cjs.entry.js +1 -1
  44. package/dist/cjs/pds-switch.cjs.entry.js +2 -2
  45. package/dist/cjs/pds-table-cell.cjs.entry.js +1 -1
  46. package/dist/cjs/pds-table-head-cell.cjs.entry.js +2 -2
  47. package/dist/cjs/pds-text.cjs.entry.js +1 -1
  48. package/dist/cjs/pds-textarea.cjs.entry.js +2 -2
  49. package/dist/cjs/pds-toast.cjs.entry.js +1 -1
  50. package/dist/cjs/pine-core.cjs.js +1 -1
  51. package/dist/cjs/{truncation-tooltip-DO9xli2e.js → truncation-tooltip-BtuiXLV0.js} +31 -6
  52. package/dist/collection/components/pds-avatar/pds-avatar.js +2 -2
  53. package/dist/collection/components/pds-button/pds-button.js +24 -22
  54. package/dist/collection/components/pds-button/stories/pds-button.stories.js +3 -3
  55. package/dist/collection/components/pds-checkbox/pds-checkbox.js +1 -1
  56. package/dist/collection/components/pds-chip/pds-chip.js +2 -2
  57. package/dist/collection/components/pds-combobox/pds-combobox.js +4 -4
  58. package/dist/collection/components/pds-datepicker/stories/pds-datepicker.stories.js +1 -1
  59. package/dist/collection/components/pds-drawer/pds-drawer.css +4 -0
  60. package/dist/collection/components/pds-dropdown-menu/pds-dropdown-menu-item/pds-dropdown-menu-item.css +8 -0
  61. package/dist/collection/components/pds-dropdown-menu/pds-dropdown-menu-item/pds-dropdown-menu-item.js +58 -6
  62. package/dist/collection/components/pds-dropdown-menu/pds-dropdown-menu.js +4 -4
  63. package/dist/collection/components/pds-dropdown-menu/stories/pds-dropdown-menu.stories.js +39 -0
  64. package/dist/collection/components/pds-modal/pds-modal-content/pds-modal-content.js +32 -30
  65. package/dist/collection/components/pds-modal/pds-modal.js +4 -4
  66. package/dist/collection/components/pds-multiselect/pds-multiselect.js +5 -5
  67. package/dist/collection/components/pds-popover/pds-popover.js +240 -11
  68. package/dist/collection/components/pds-popover/stories/pds-popover.stories.js +31 -0
  69. package/dist/collection/components/pds-radio/pds-radio.js +2 -2
  70. package/dist/collection/components/pds-radio-group/pds-radio-group.js +1 -1
  71. package/dist/collection/components/pds-switch/pds-switch.js +1 -1
  72. package/dist/collection/components/pds-table/pds-table-head-cell/pds-table-head-cell.js +1 -1
  73. package/dist/collection/components/pds-table/stories/pds-table.stories.js +1 -1
  74. package/dist/collection/components/pds-text/stories/pds-text.stories.js +1 -1
  75. package/dist/collection/components/pds-textarea/pds-textarea.js +1 -1
  76. package/dist/collection/components/pds-toast/pds-toast.js +1 -1
  77. package/dist/collection/utils/truncation-tooltip.js +31 -6
  78. package/dist/docs.json +124 -7
  79. package/dist/esm/{index-BJXk71tO.js → index-BWDu62Mo.js} +1 -1
  80. package/dist/esm/loader.js +1 -1
  81. package/dist/esm/pds-accordion.entry.js +1 -1
  82. package/dist/esm/pds-avatar.entry.js +3 -3
  83. package/dist/esm/pds-button.entry.js +24 -21
  84. package/dist/esm/pds-checkbox.entry.js +2 -2
  85. package/dist/esm/pds-chip.entry.js +4 -4
  86. package/dist/esm/pds-combobox.entry.js +4 -4
  87. package/dist/esm/pds-copytext.entry.js +2 -2
  88. package/dist/esm/pds-datepicker.entry.js +1 -1
  89. package/dist/esm/pds-drawer.entry.js +1 -1
  90. package/dist/esm/pds-dropdown-menu-item.entry.js +31 -2
  91. package/dist/esm/pds-dropdown-menu.entry.js +4 -4
  92. package/dist/esm/pds-filter.entry.js +1 -1
  93. package/dist/esm/pds-input.entry.js +1 -1
  94. package/dist/esm/pds-link.entry.js +1 -1
  95. package/dist/esm/pds-modal-content.entry.js +32 -30
  96. package/dist/esm/pds-modal.entry.js +4 -4
  97. package/dist/esm/pds-multiselect.entry.js +6 -6
  98. package/dist/esm/pds-popover.entry.js +187 -11
  99. package/dist/esm/pds-radio-group.entry.js +2 -2
  100. package/dist/esm/pds-radio.entry.js +3 -3
  101. package/dist/esm/pds-select.entry.js +1 -1
  102. package/dist/esm/pds-sortable-item.entry.js +1 -1
  103. package/dist/esm/pds-switch.entry.js +2 -2
  104. package/dist/esm/pds-table-cell.entry.js +1 -1
  105. package/dist/esm/pds-table-head-cell.entry.js +2 -2
  106. package/dist/esm/pds-text.entry.js +1 -1
  107. package/dist/esm/pds-textarea.entry.js +2 -2
  108. package/dist/esm/pds-toast.entry.js +1 -1
  109. package/dist/esm/pine-core.js +1 -1
  110. package/dist/esm/{truncation-tooltip-DRXsdKk1.js → truncation-tooltip-FaW67DiY.js} +31 -6
  111. package/dist/esm-es5/loader.js +1 -1
  112. package/dist/esm-es5/pds-accordion.entry.js +1 -1
  113. package/dist/esm-es5/pds-avatar.entry.js +1 -1
  114. package/dist/esm-es5/pds-button.entry.js +1 -1
  115. package/dist/esm-es5/pds-checkbox.entry.js +1 -1
  116. package/dist/esm-es5/pds-chip.entry.js +1 -1
  117. package/dist/esm-es5/pds-combobox.entry.js +1 -1
  118. package/dist/esm-es5/pds-copytext.entry.js +1 -1
  119. package/dist/esm-es5/pds-datepicker.entry.js +1 -1
  120. package/dist/esm-es5/pds-drawer.entry.js +1 -1
  121. package/dist/esm-es5/pds-dropdown-menu-item.entry.js +1 -1
  122. package/dist/esm-es5/pds-dropdown-menu.entry.js +1 -1
  123. package/dist/esm-es5/pds-filter.entry.js +1 -1
  124. package/dist/esm-es5/pds-input.entry.js +1 -1
  125. package/dist/esm-es5/pds-link.entry.js +1 -1
  126. package/dist/esm-es5/pds-modal-content.entry.js +1 -1
  127. package/dist/esm-es5/pds-modal.entry.js +1 -1
  128. package/dist/esm-es5/pds-multiselect.entry.js +1 -1
  129. package/dist/esm-es5/pds-popover.entry.js +1 -1
  130. package/dist/esm-es5/pds-radio-group.entry.js +1 -1
  131. package/dist/esm-es5/pds-radio.entry.js +1 -1
  132. package/dist/esm-es5/pds-select.entry.js +1 -1
  133. package/dist/esm-es5/pds-sortable-item.entry.js +1 -1
  134. package/dist/esm-es5/pds-switch.entry.js +1 -1
  135. package/dist/esm-es5/pds-table-cell.entry.js +1 -1
  136. package/dist/esm-es5/pds-table-head-cell.entry.js +1 -1
  137. package/dist/esm-es5/pds-text.entry.js +1 -1
  138. package/dist/esm-es5/pds-textarea.entry.js +1 -1
  139. package/dist/esm-es5/pds-toast.entry.js +1 -1
  140. package/dist/esm-es5/pine-core.js +1 -1
  141. package/dist/esm-es5/truncation-tooltip-FaW67DiY.js +1 -0
  142. package/dist/pine-core/{p-e836f3ec.system.entry.js → p-00a50de5.system.entry.js} +2 -2
  143. package/dist/pine-core/{p-1437585e.system.entry.js → p-047433a4.system.entry.js} +1 -1
  144. package/dist/pine-core/{p-7fa66cad.entry.js → p-089c3e60.entry.js} +1 -1
  145. package/dist/pine-core/{p-341c38f1.system.entry.js → p-0af0d752.system.entry.js} +1 -1
  146. package/dist/pine-core/{p-3a959459.system.entry.js → p-121612a7.system.entry.js} +1 -1
  147. package/dist/pine-core/{p-cde8eb81.entry.js → p-138b278b.entry.js} +1 -1
  148. package/dist/pine-core/{p-cfd012d4.entry.js → p-158b2f51.entry.js} +1 -1
  149. package/dist/pine-core/p-17f97f1a.entry.js +1 -0
  150. package/dist/pine-core/{p-5c90091e.system.entry.js → p-19ba2788.system.entry.js} +1 -1
  151. package/dist/pine-core/p-1dc5a7e0.system.entry.js +1 -0
  152. package/dist/pine-core/{p-355e4cf9.entry.js → p-25ea48d1.entry.js} +1 -1
  153. package/dist/pine-core/{p-c81d8ac8.entry.js → p-295f64ea.entry.js} +1 -1
  154. package/dist/pine-core/{p-576cf904.system.entry.js → p-29cd4113.system.entry.js} +1 -1
  155. package/dist/pine-core/{p-1ee86d87.system.entry.js → p-332590c7.system.entry.js} +1 -1
  156. package/dist/pine-core/p-36cb4bf6.entry.js +1 -0
  157. package/dist/pine-core/{p-080cb087.system.entry.js → p-3c977d8d.system.entry.js} +1 -1
  158. package/dist/pine-core/p-3d51891a.entry.js +1 -0
  159. package/dist/pine-core/{p-72865997.entry.js → p-44f546ae.entry.js} +1 -1
  160. package/dist/pine-core/{p-b60ac7eb.system.entry.js → p-4bfc8b3e.system.entry.js} +1 -1
  161. package/dist/pine-core/p-5833a072.system.entry.js +1 -0
  162. package/dist/pine-core/p-5cfd4d41.system.entry.js +1 -0
  163. package/dist/pine-core/{p-f2c253d8.entry.js → p-62433f32.entry.js} +1 -1
  164. package/dist/pine-core/{p-ebd0bff2.entry.js → p-631f5bcc.entry.js} +1 -1
  165. package/dist/pine-core/p-717323e4.entry.js +1 -0
  166. package/dist/pine-core/{p-e7a95a49.system.entry.js → p-719542e1.system.entry.js} +1 -1
  167. package/dist/pine-core/p-73f931a1.entry.js +1 -0
  168. package/dist/pine-core/{p-f77905aa.system.entry.js → p-884cb31d.system.entry.js} +1 -1
  169. package/dist/pine-core/{p-4435fff6.system.entry.js → p-8987fd45.system.entry.js} +1 -1
  170. package/dist/pine-core/{p-02088776.entry.js → p-8c588ca0.entry.js} +1 -1
  171. package/dist/pine-core/p-8e3ff75d.entry.js +1 -0
  172. package/dist/pine-core/{p-bd26a5e8.entry.js → p-90d5edc6.entry.js} +1 -1
  173. package/dist/pine-core/p-91f60173.entry.js +1 -0
  174. package/dist/pine-core/{p-258770cf.system.entry.js → p-9265497e.system.entry.js} +1 -1
  175. package/dist/pine-core/p-9713fb36.entry.js +1 -0
  176. package/dist/pine-core/p-99fdf70f.entry.js +1 -0
  177. package/dist/pine-core/{p-0a84d220.entry.js → p-9aaedded.entry.js} +1 -1
  178. package/dist/pine-core/p-9df5e204.entry.js +1 -0
  179. package/dist/pine-core/p-CuSNnuFu.system.js +1 -1
  180. package/dist/pine-core/p-D04W0cAO.system.js +1 -0
  181. package/dist/pine-core/{p-6d603d16.system.entry.js → p-acca5f7b.system.entry.js} +1 -1
  182. package/dist/pine-core/{p-a19cd59c.entry.js → p-aff411d0.entry.js} +1 -1
  183. package/dist/pine-core/{p-bb73ad66.entry.js → p-b182f806.entry.js} +1 -1
  184. package/dist/pine-core/{p-01aa382c.system.entry.js → p-b9d145f8.system.entry.js} +1 -1
  185. package/dist/pine-core/p-bdac8f2b.entry.js +1 -0
  186. package/dist/pine-core/{p-aa032a5e.system.entry.js → p-be9b6e63.system.entry.js} +1 -1
  187. package/dist/pine-core/{p-ff3de72e.entry.js → p-c483997a.entry.js} +1 -1
  188. package/dist/pine-core/p-c4e12f0a.entry.js +1 -0
  189. package/dist/pine-core/{p-e8cb76e8.entry.js → p-c97bfac9.entry.js} +1 -1
  190. package/dist/pine-core/{p-bd67156b.system.entry.js → p-ca4ee736.system.entry.js} +1 -1
  191. package/dist/pine-core/p-cccfe60f.system.entry.js +1 -0
  192. package/dist/pine-core/{p-c5c9ed36.system.entry.js → p-d3d69ca4.system.entry.js} +1 -1
  193. package/dist/pine-core/{p-f0d24298.system.entry.js → p-ea340c55.system.entry.js} +1 -1
  194. package/dist/pine-core/{p-88ff1604.entry.js → p-ea8f5d29.entry.js} +1 -1
  195. package/dist/pine-core/{p-d8570b4b.system.entry.js → p-eaf85be6.system.entry.js} +1 -1
  196. package/dist/pine-core/{p-fc25b956.system.entry.js → p-ecc7a531.system.entry.js} +1 -1
  197. package/dist/pine-core/{p-cfc52b95.system.entry.js → p-ee0e30bf.system.entry.js} +1 -1
  198. package/dist/pine-core/{p-e803f629.system.entry.js → p-ef13f665.system.entry.js} +1 -1
  199. package/dist/pine-core/{p-97d0b89e.system.entry.js → p-f3067d90.system.entry.js} +1 -1
  200. package/dist/pine-core/{p-JtdHT_W5.js → p-jrkGLEPe.js} +1 -1
  201. package/dist/pine-core/pine-core.esm.js +1 -1
  202. package/dist/types/components/pds-button/pds-button.d.ts +3 -4
  203. package/dist/types/components/pds-dropdown-menu/pds-dropdown-menu-item/pds-dropdown-menu-item.d.ts +13 -3
  204. package/dist/types/components/pds-modal/pds-modal-content/pds-modal-content.d.ts +2 -0
  205. package/dist/types/components/pds-popover/pds-popover.d.ts +43 -1
  206. package/dist/types/components.d.ts +43 -10
  207. package/dist/types/utils/truncation-tooltip.d.ts +4 -4
  208. package/dist/types/utils/types.d.ts +1 -0
  209. package/dist/vscode.html-data.json +50 -7
  210. package/hydrate/index.js +341 -104
  211. package/hydrate/index.mjs +341 -104
  212. package/package.json +2 -2
  213. package/dist/esm-es5/truncation-tooltip-DRXsdKk1.js +0 -1
  214. package/dist/pine-core/p-153ba77b.entry.js +0 -1
  215. package/dist/pine-core/p-645a482b.system.entry.js +0 -1
  216. package/dist/pine-core/p-74a05bcc.entry.js +0 -1
  217. package/dist/pine-core/p-7bf6563a.system.entry.js +0 -1
  218. package/dist/pine-core/p-80a698ae.entry.js +0 -1
  219. package/dist/pine-core/p-82cf4ec6.entry.js +0 -1
  220. package/dist/pine-core/p-8d292b48.entry.js +0 -1
  221. package/dist/pine-core/p-92ae6082.entry.js +0 -1
  222. package/dist/pine-core/p-993d1043.entry.js +0 -1
  223. package/dist/pine-core/p-a787b34a.system.entry.js +0 -1
  224. package/dist/pine-core/p-b69f04b7.entry.js +0 -1
  225. package/dist/pine-core/p-beb23781.entry.js +0 -1
  226. package/dist/pine-core/p-bf5887e8.system.entry.js +0 -1
  227. package/dist/pine-core/p-c36b490e.entry.js +0 -1
  228. package/dist/pine-core/p-ca0d48e8.entry.js +0 -1
  229. package/dist/pine-core/p-f474d7de.entry.js +0 -1
  230. package/dist/pine-core/p-qhdoK1Jp.system.js +0 -1
  231. /package/dist/esm-es5/{index-BJXk71tO.js → index-BWDu62Mo.js} +0 -0
  232. /package/dist/pine-core/{p-BJXk71tO.js → p-BWDu62Mo.js} +0 -0
  233. /package/dist/pine-core/{p-n3_ytl_p.system.js → p-ClDiSG97.system.js} +0 -0
@@ -4,8 +4,12 @@ import { restoreReflectedDefault } from "../../utils/reflected-attribute-default
4
4
  import { setAttributesIfChanged } from "../../utils/attributes";
5
5
  import { cssEscape } from "../../utils/css";
6
6
  import { limitRestores } from "../../utils/keep-hydrated";
7
- /** Open auto popovers, in the order they opened: Escape goes to the last one it applies to. */
8
- const openAutoPopovers = [];
7
+ /** Open popovers that close on Escape, in the order they opened: Escape goes to the last one it applies to. */
8
+ const popoversTakingEscape = [];
9
+ /** Hovering the trigger waits this long before opening, so a pointer passing over it doesn't open it. */
10
+ const HOVER_OPEN_DELAY_MS = 120;
11
+ /** Leaving the trigger and panel waits this long before closing, which covers the gap between them. */
12
+ const HOVER_CLOSE_DELAY_MS = 200;
9
13
  /** The focused element, followed down through shadow roots to the control that has focus. */
10
14
  function deepActiveElement() {
11
15
  var _a;
@@ -66,11 +70,29 @@ export class PdsPopover {
66
70
  * Timestamp when popover was opened (for preventing immediate close)
67
71
  */
68
72
  this.openTimestamp = 0;
73
+ /**
74
+ * Set while hover holds the popover open, until a click pins it or it closes
75
+ */
76
+ this.hoverOpened = false;
77
+ /**
78
+ * Set when the pointer left while focus was in the panel, so focus leaving it closes it instead
79
+ */
80
+ this.closeWhenFocusLeaves = false;
81
+ /**
82
+ * How far the pointer was from the panel at its last move after leaving the trigger
83
+ */
84
+ this.panelDistance = Infinity;
69
85
  /**
70
86
  * Determines the action that triggers the popover. For manual popovers, the consumer is responsible for toggling this value.
71
87
  * @defaultValue "show"
72
88
  */
73
89
  this.popoverTargetAction = 'show';
90
+ /**
91
+ * Determines how the popover opens. With `hover`, it also opens when the pointer rests on the trigger and closes
92
+ * when the pointer leaves, until a click on the trigger keeps it open. Enter or Space on the trigger opens it either way.
93
+ * @defaultValue "click"
94
+ */
95
+ this.openOn = 'click';
74
96
  /**
75
97
  * Determines the type of popover. Auto popovers can be "light dismissed" by clicking outside of the popover.
76
98
  * Manual popovers require the consumer to handle the visibility of the popover.
@@ -81,6 +103,11 @@ export class PdsPopover {
81
103
  * @defaultValue 352
82
104
  */
83
105
  this.maxWidth = 352;
106
+ /**
107
+ * Sets the padding of the popover content
108
+ * @defaultValue "md"
109
+ */
110
+ this.padding = 'md';
84
111
  /**
85
112
  * Determines the preferred position of the popover
86
113
  * @defaultValue "right"
@@ -95,6 +122,7 @@ export class PdsPopover {
95
122
  // Clean up previous trigger's event listener if it exists
96
123
  if (this.triggerEl != null) {
97
124
  this.triggerEl.removeEventListener('click', this.handleTriggerClick);
125
+ this.stopListeningForHover(this.triggerEl);
98
126
  }
99
127
  if (assignedElements.length > 0) {
100
128
  // Use the first assigned element as the trigger
@@ -102,6 +130,7 @@ export class PdsPopover {
102
130
  this.linkTriggerToPortal();
103
131
  // Attach click listener to handle popover visibility
104
132
  this.triggerEl.addEventListener('click', this.handleTriggerClick);
133
+ this.listenForHover(this.triggerEl);
105
134
  }
106
135
  else {
107
136
  this.triggerEl = null;
@@ -128,6 +157,13 @@ export class PdsPopover {
128
157
  if (!anchorWithHref) {
129
158
  event.preventDefault();
130
159
  }
160
+ if (this.openOn === 'hover' && this.popoverTargetAction !== 'hide') {
161
+ const heldByHover = this.hoverOpened;
162
+ this.pin();
163
+ // Pinning, not toggling: the hover before a click has already opened it.
164
+ if (heldByHover)
165
+ return;
166
+ }
131
167
  // Execute the appropriate action based on popoverTargetAction prop
132
168
  switch (this.popoverTargetAction) {
133
169
  case 'show':
@@ -142,6 +178,60 @@ export class PdsPopover {
142
178
  break;
143
179
  }
144
180
  };
181
+ // A tap sends touch pointer events and then a click, so touch takes the click path alone.
182
+ this.handleHoverEnter = (event) => {
183
+ if (event.pointerType === 'touch')
184
+ return;
185
+ this.clearHoverTimers();
186
+ if (this.active)
187
+ return;
188
+ this.hoverOpenTimer = setTimeout(() => {
189
+ this.hoverOpenTimer = undefined;
190
+ if (this.active || this.portalEl == null)
191
+ return;
192
+ this.hoverOpened = true;
193
+ this.show();
194
+ }, HOVER_OPEN_DELAY_MS);
195
+ };
196
+ this.handleHoverLeave = (event) => {
197
+ if (event.pointerType === 'touch')
198
+ return;
199
+ this.clearHoverTimers();
200
+ if (!this.hoverOpened)
201
+ return;
202
+ this.hoverCloseTimer = setTimeout(this.closeFromHover, HOVER_CLOSE_DELAY_MS);
203
+ if (event.currentTarget !== this.triggerEl)
204
+ return;
205
+ this.panelDistance = this.distanceToPanel(event);
206
+ document.addEventListener('pointermove', this.handleMoveTowardPanel);
207
+ };
208
+ // A slow diagonal from the trigger to a panel shorter than it runs beside the panel for longer
209
+ // than the close delay, so each move that brings the pointer closer starts the delay again.
210
+ this.handleMoveTowardPanel = (event) => {
211
+ const distance = this.distanceToPanel(event);
212
+ if (distance < this.panelDistance) {
213
+ clearTimeout(this.hoverCloseTimer);
214
+ this.hoverCloseTimer = setTimeout(this.closeFromHover, HOVER_CLOSE_DELAY_MS);
215
+ }
216
+ this.panelDistance = distance;
217
+ };
218
+ this.handleHoverFocusOut = (event) => {
219
+ if (!this.closeWhenFocusLeaves || containsComposed(this.portalEl, event.relatedTarget))
220
+ return;
221
+ this.closeWhenFocusLeaves = false;
222
+ this.hoverCloseTimer = setTimeout(this.closeFromHover, HOVER_CLOSE_DELAY_MS);
223
+ };
224
+ this.closeFromHover = () => {
225
+ this.clearHoverTimers();
226
+ if (!this.hoverOpened)
227
+ return;
228
+ // Content shown on hover stays while focus is in it (WCAG 1.4.13).
229
+ if (containsComposed(this.portalEl, deepActiveElement())) {
230
+ this.closeWhenFocusLeaves = true;
231
+ return;
232
+ }
233
+ this.hide();
234
+ };
145
235
  /** The focus style copied into a shadow root the panel opened in, which `document.head` can't reach. */
146
236
  this.shadowFocusStyle = null;
147
237
  /** The dialog the panel was opened in, closed along with it. */
@@ -163,6 +253,32 @@ export class PdsPopover {
163
253
  }
164
254
  };
165
255
  }
256
+ openOnChanged() {
257
+ this.pin();
258
+ this.stopListeningForHover(this.triggerEl);
259
+ this.stopListeningForHover(this.portalEl);
260
+ // Out of the page, connectedCallback binds them when it comes back.
261
+ if (!this.isComponentMounted)
262
+ return;
263
+ this.listenForHover(this.triggerEl);
264
+ this.listenForHover(this.portalEl);
265
+ }
266
+ applyPadding() {
267
+ if (this.portalEl != null)
268
+ this.portalEl.style.padding = this.paddingToken();
269
+ }
270
+ paddingToken() {
271
+ switch (this.padding) {
272
+ case 'none':
273
+ return 'var(--pine-dimension-none)';
274
+ case 'xs':
275
+ return 'var(--pine-dimension-xs)';
276
+ case 'sm':
277
+ return 'var(--pine-dimension-sm)';
278
+ default:
279
+ return 'var(--pine-dimension-md)';
280
+ }
281
+ }
166
282
  restorePlacementDefault(newValue) {
167
283
  this.placement = restoreReflectedDefault(this.el, 'placement', newValue, 'right');
168
284
  }
@@ -213,6 +329,48 @@ export class PdsPopover {
213
329
  // Skips values already in place, so the trigger observer's own callback doesn't loop.
214
330
  return setAttributesIfChanged(this.triggerEl, { 'aria-expanded': String(this.active), 'aria-controls': this.portalEl.id });
215
331
  }
332
+ listenForHover(el) {
333
+ if (this.openOn !== 'hover' || el == null)
334
+ return;
335
+ el.addEventListener('pointerenter', this.handleHoverEnter);
336
+ el.addEventListener('pointerleave', this.handleHoverLeave);
337
+ el.addEventListener('focusout', this.handleHoverFocusOut);
338
+ }
339
+ stopListeningForHover(el) {
340
+ if (el == null)
341
+ return;
342
+ el.removeEventListener('pointerenter', this.handleHoverEnter);
343
+ el.removeEventListener('pointerleave', this.handleHoverLeave);
344
+ el.removeEventListener('focusout', this.handleHoverFocusOut);
345
+ }
346
+ distanceToPanel({ clientX, clientY }) {
347
+ const panel = this.portalEl.getBoundingClientRect();
348
+ return Math.hypot(Math.max(panel.left - clientX, 0, clientX - panel.right), Math.max(panel.top - clientY, 0, clientY - panel.bottom));
349
+ }
350
+ /**
351
+ * Keeps a popover that hover opened open, closing from then on as one opened by a click does. A
352
+ * manual one gives up Escape again, which it took only while hover held it.
353
+ */
354
+ pin() {
355
+ var _a;
356
+ this.clearHoverTimers();
357
+ if (!this.hoverOpened)
358
+ return;
359
+ this.hoverOpened = false;
360
+ if (this.popoverType === 'auto')
361
+ return;
362
+ this.forgetOpen();
363
+ (_a = this.portalEl) === null || _a === void 0 ? void 0 : _a.removeAttribute('data-pds-popover-open');
364
+ document.removeEventListener('keydown', this.boundEscapeKeyHandler);
365
+ }
366
+ clearHoverTimers() {
367
+ clearTimeout(this.hoverOpenTimer);
368
+ clearTimeout(this.hoverCloseTimer);
369
+ this.hoverOpenTimer = undefined;
370
+ this.hoverCloseTimer = undefined;
371
+ this.closeWhenFocusLeaves = false;
372
+ document.removeEventListener('pointermove', this.handleMoveTowardPanel);
373
+ }
216
374
  // componentDidLoad doesn't run again when a popover comes back, so rebuild whatever
217
375
  // disconnectedCallback tore down. Each step is a no-op when nothing was.
218
376
  connectedCallback() {
@@ -222,6 +380,7 @@ export class PdsPopover {
222
380
  this.isComponentMounted = true;
223
381
  this.createPortal();
224
382
  (_a = this.triggerEl) === null || _a === void 0 ? void 0 : _a.addEventListener('click', this.handleTriggerClick);
383
+ this.listenForHover(this.triggerEl);
225
384
  this.observeTrigger();
226
385
  this.linkTriggerToPortal();
227
386
  // Still open after a move: the trigger moved, so the portal follows it, into the dialog it
@@ -256,6 +415,7 @@ export class PdsPopover {
256
415
  if (this.triggerEl != null) {
257
416
  this.triggerEl.removeEventListener('click', this.handleTriggerClick);
258
417
  }
418
+ this.stopListeningForHover(this.triggerEl);
259
419
  (_a = this.triggerObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
260
420
  this.triggerObserver = null;
261
421
  // Clean up all event listeners
@@ -264,6 +424,8 @@ export class PdsPopover {
264
424
  document.removeEventListener('submit', this.handleSubmitFromPanel, true);
265
425
  // Clear any pending debounce timers
266
426
  this.clearDebounceTimers();
427
+ this.clearHoverTimers();
428
+ this.hoverOpened = false;
267
429
  // Remove portal from DOM
268
430
  this.removePortal();
269
431
  }
@@ -297,7 +459,7 @@ export class PdsPopover {
297
459
  this.portalEl.style.borderRadius = 'var(--pine-dimension-125)';
298
460
  this.portalEl.style.boxShadow = 'var(--pine-box-shadow-200)';
299
461
  this.portalEl.style.margin = 'var(--pine-dimension-none)';
300
- this.portalEl.style.padding = 'var(--pine-dimension-md)';
462
+ this.portalEl.style.padding = this.paddingToken();
301
463
  // Generate unique ID
302
464
  if (!this.componentId) {
303
465
  (_a = this.generatedPortalId) !== null && _a !== void 0 ? _a : (this.generatedPortalId = `pds-popover-portal-${PdsPopover.instanceCounter++}`);
@@ -311,6 +473,7 @@ export class PdsPopover {
311
473
  // Add global focus styles to match Pine design system
312
474
  // This is done after appending to ensure it's part of the document and can access CSS variables
313
475
  this.addPortalFocusStyles();
476
+ this.listenForHover(this.portalEl);
314
477
  }
315
478
  /**
316
479
  * Adds Pine design system focus styles and dark mode border to the portal element
@@ -438,8 +601,8 @@ export class PdsPopover {
438
601
  // Update portal content with latest slot content
439
602
  this.updatePortalContent();
440
603
  // Show portal. The attribute tells a modal around it that this popover takes Escape first; only
441
- // an auto popover listens for Escape.
442
- if (this.popoverType === 'auto')
604
+ // an auto popover, or one hover opened, listens for Escape.
605
+ if (this.popoverType === 'auto' || this.hoverOpened)
443
606
  this.portalEl.setAttribute('data-pds-popover-open', '');
444
607
  this.portalEl.style.display = 'block';
445
608
  this.portalEl.style.opacity = '1';
@@ -453,11 +616,16 @@ export class PdsPopover {
453
616
  // Add scroll and resize listeners for repositioning (always)
454
617
  this.addScrollAndResizeListeners();
455
618
  document.addEventListener('submit', this.handleSubmitFromPanel, true);
456
- // Add document listeners for light dismiss and escape key (auto type only)
619
+ // Add document listeners for light dismiss and escape key (auto type only), or for the escape
620
+ // key alone while hover holds a manual popover open
457
621
  if (this.popoverType === 'auto') {
458
- openAutoPopovers.push(this);
622
+ popoversTakingEscape.push(this);
459
623
  this.addLightDismissListeners();
460
624
  }
625
+ else if (this.hoverOpened) {
626
+ popoversTakingEscape.push(this);
627
+ document.addEventListener('keydown', this.boundEscapeKeyHandler);
628
+ }
461
629
  // Emit open event
462
630
  this.pdsPopoverOpen.emit({
463
631
  componentId: this.componentId,
@@ -468,9 +636,11 @@ export class PdsPopover {
468
636
  * Closes the popover programmatically
469
637
  */
470
638
  async hide() {
639
+ this.clearHoverTimers();
471
640
  if (!this.active || !this.portalEl)
472
641
  return;
473
642
  this.active = false;
643
+ this.hoverOpened = false;
474
644
  this.unwatchDialogClose();
475
645
  // Restoring the content drops its focus to the body, so check where focus is first. In a
476
646
  // shadow root, document.activeElement is only its host.
@@ -549,9 +719,9 @@ export class PdsPopover {
549
719
  this.portalEl.style.border = root instanceof ShadowRoot && closest('[data-theme="dark"]', this.el) ? 'var(--pine-border)' : '';
550
720
  }
551
721
  forgetOpen() {
552
- const index = openAutoPopovers.indexOf(this);
722
+ const index = popoversTakingEscape.indexOf(this);
553
723
  if (index >= 0)
554
- openAutoPopovers.splice(index, 1);
724
+ popoversTakingEscape.splice(index, 1);
555
725
  }
556
726
  /**
557
727
  * Whether an Escape is this popover's. One from a dialog the popover isn't in belongs to that
@@ -660,7 +830,7 @@ export class PdsPopover {
660
830
  return;
661
831
  // Like the native Popover API, Escape closes only the top popover: the last one opened that
662
832
  // it applies to. Every open popover hears it, so each checks whether it's that one.
663
- const top = [...openAutoPopovers].reverse().find(popover => popover.takesEscapeFrom(event));
833
+ const top = [...popoversTakingEscape].reverse().find(popover => popover.takesEscapeFrom(event));
664
834
  if (top !== this)
665
835
  return;
666
836
  // Take the key, so the dialog or modal around it doesn't close too.
@@ -785,7 +955,7 @@ export class PdsPopover {
785
955
  }, 16); // ~1 frame at 60fps
786
956
  }
787
957
  render() {
788
- return (h(Host, { key: 'da8a0c715df1199231b1265085f3f9ae0234032d', id: this.componentId }, h("span", { key: 'f35023492399420829cec2e88556462f5ac1663e', class: "pds-popover__trigger-wrapper" }, h("slot", { key: '38ebe92eebb87665467b3f92ff94dc8095450063', name: "trigger", onSlotchange: this.handleTriggerSlotChange })), h("div", { key: '8917930f4520544d14bbd68b4c7936dd60af19e3', class: "pds-popover__content-slot-wrapper" }, h("slot", { key: '28d3f9112256edd95721a79a7072a9e35a3712bf', onSlotchange: this.handleContentSlotChange }))));
958
+ return (h(Host, { key: 'e48b897acdc9e89f2e6b1449e7a358da7da1060c', id: this.componentId }, h("span", { key: 'b83b2c234b07703c2c850a9991c94939ed2fdaf7', class: "pds-popover__trigger-wrapper" }, h("slot", { key: '0a7f7829ee445fd57cf824ebee1409b3c571f983', name: "trigger", onSlotchange: this.handleTriggerSlotChange })), h("div", { key: '8c1fe2d4d7c4342b4bd5eb4c56e596f17b4e14fa', class: "pds-popover__content-slot-wrapper" }, h("slot", { key: '4ccfce1f335acbc436f7942e497f5d360f849d48', onSlotchange: this.handleContentSlotChange }))));
789
959
  }
790
960
  static get is() { return "pds-popover"; }
791
961
  static get encapsulation() { return "shadow"; }
@@ -824,6 +994,29 @@ export class PdsPopover {
824
994
  "attribute": "popover-target-action",
825
995
  "defaultValue": "'show'"
826
996
  },
997
+ "openOn": {
998
+ "type": "string",
999
+ "mutable": false,
1000
+ "complexType": {
1001
+ "original": "'click' | 'hover'",
1002
+ "resolved": "\"click\" | \"hover\"",
1003
+ "references": {}
1004
+ },
1005
+ "required": false,
1006
+ "optional": true,
1007
+ "docs": {
1008
+ "tags": [{
1009
+ "name": "defaultValue",
1010
+ "text": "\"click\""
1011
+ }],
1012
+ "text": "Determines how the popover opens. With `hover`, it also opens when the pointer rests on the trigger and closes\nwhen the pointer leaves, until a click on the trigger keeps it open. Enter or Space on the trigger opens it either way."
1013
+ },
1014
+ "getter": false,
1015
+ "setter": false,
1016
+ "reflect": false,
1017
+ "attribute": "open-on",
1018
+ "defaultValue": "'click'"
1019
+ },
827
1020
  "popoverType": {
828
1021
  "type": "string",
829
1022
  "mutable": false,
@@ -886,6 +1079,36 @@ export class PdsPopover {
886
1079
  "attribute": "max-width",
887
1080
  "defaultValue": "352"
888
1081
  },
1082
+ "padding": {
1083
+ "type": "string",
1084
+ "mutable": false,
1085
+ "complexType": {
1086
+ "original": "PopoverPaddingType",
1087
+ "resolved": "\"md\" | \"none\" | \"sm\" | \"xs\"",
1088
+ "references": {
1089
+ "PopoverPaddingType": {
1090
+ "location": "import",
1091
+ "path": "@utils/types",
1092
+ "id": "src/utils/types.ts::PopoverPaddingType",
1093
+ "referenceLocation": "PopoverPaddingType"
1094
+ }
1095
+ }
1096
+ },
1097
+ "required": false,
1098
+ "optional": true,
1099
+ "docs": {
1100
+ "tags": [{
1101
+ "name": "defaultValue",
1102
+ "text": "\"md\""
1103
+ }],
1104
+ "text": "Sets the padding of the popover content"
1105
+ },
1106
+ "getter": false,
1107
+ "setter": false,
1108
+ "reflect": false,
1109
+ "attribute": "padding",
1110
+ "defaultValue": "'md'"
1111
+ },
889
1112
  "placement": {
890
1113
  "type": "string",
891
1114
  "mutable": true,
@@ -1028,6 +1251,12 @@ export class PdsPopover {
1028
1251
  static get elementRef() { return "el"; }
1029
1252
  static get watchers() {
1030
1253
  return [{
1254
+ "propName": "openOn",
1255
+ "methodName": "openOnChanged"
1256
+ }, {
1257
+ "propName": "padding",
1258
+ "methodName": "applyPadding"
1259
+ }, {
1031
1260
  "propName": "placement",
1032
1261
  "methodName": "restorePlacementDefault"
1033
1262
  }];
@@ -6,9 +6,18 @@ export default {
6
6
  args: {
7
7
  popoverTargetAction: 'toggle',
8
8
  popoverType: 'auto',
9
+ openOn: 'click',
9
10
  placement: 'bottom-start',
11
+ padding: 'md',
10
12
  componentId: 'popover-playground',
11
13
  },
14
+ argTypes: {
15
+ padding: {
16
+ control: { type: 'select' },
17
+ options: ['none', 'xs', 'sm', 'md'],
18
+ },
19
+ openOn: { control: 'inline-radio', options: ['click', 'hover'] },
20
+ },
12
21
  component: 'pds-popover',
13
22
  parameters: {},
14
23
  title: 'components/Popover'
@@ -19,8 +28,10 @@ const BaseTemplate = (args) => html`
19
28
  component-id=${args.componentId}
20
29
  popover-target-action=${args.popoverTargetAction}
21
30
  popover-type=${args.popoverType}
31
+ open-on=${args.openOn}
22
32
  placement=${args.placement}
23
33
  .maxWidth=${args.maxWidth}
34
+ padding=${args.padding}
24
35
  >
25
36
  <pds-button slot="trigger">Show popover</pds-button>
26
37
  <p>This is popover content that can be configured using the controls below.</p>
@@ -84,3 +95,23 @@ export const WithContent = (args) => html`
84
95
  WithContent.args = {
85
96
  componentId: 'popover-rich'
86
97
  };
98
+
99
+ export const OpenOnHover = (args) => html`
100
+ <pds-popover
101
+ component-id=${args.componentId || 'popover-hover'}
102
+ open-on="hover"
103
+ popover-target-action=${args.popoverTargetAction}
104
+ popover-type=${args.popoverType}
105
+ placement=${args.placement}
106
+ .maxWidth=${args.maxWidth}
107
+ >
108
+ <pds-button slot="trigger" variant="secondary">Hover for details</pds-button>
109
+ <pds-box direction="column" gap="xs" fit="true">
110
+ <pds-text tag="h3" size="md" weight="semibold">Project summary</pds-text>
111
+ <pds-text tag="p" size="sm" color="secondary">Rest the pointer on the trigger to preview supporting details. Click the trigger to keep this open while you read.</pds-text>
112
+ </pds-box>
113
+ </pds-popover>
114
+ `;
115
+ OpenOnHover.args = {
116
+ componentId: 'popover-hover'
117
+ };
@@ -101,11 +101,11 @@ export class PdsRadio {
101
101
  const renderLabelAndMessages = () => [
102
102
  h("label", { htmlFor: this.componentId, key: `${this.componentId}-label` }, h("input", { key: 'ccd618855831bcee309e815f2cd2712fd4e8fe1f', "aria-describedby": assignDescription(this.componentId, this.invalid, this.helperMessage, this.errorMessage), "aria-invalid": this.invalid ? "true" : undefined, type: "radio", id: this.componentId, name: this.name, value: this.value, checked: this.checked, required: this.required, disabled: this.disabled, onChange: this.handleRadioChange, class: this.hasImage() ? 'visually-hidden' : '' }), h("span", { key: '2e372a880c28f48fb804bac2518208082c141fad', class: this.hideLabel ? 'visually-hidden' : '' }, this.label)),
103
103
  this.helperMessage && (h("div", { key: `${this.componentId}-helper`, class: 'pds-radio__message', id: messageId(this.componentId, 'helper') }, this.helperMessage)),
104
- this.errorMessage && (h("div", { key: `${this.componentId}-error`, class: `pds-radio__message pds-radio__message--error`, id: messageId(this.componentId, 'error'), "aria-live": "assertive" }, h("pds-icon", { key: '889d502575f6a162f450b6b58d76a6f6f1c74e4b', icon: danger, size: "small" }), this.errorMessage))
104
+ this.errorMessage && (h("div", { key: `${this.componentId}-error`, class: `pds-radio__message pds-radio__message--error`, id: messageId(this.componentId, 'error'), "aria-live": "assertive" }, h("pds-icon", { key: '08e7a2319eddc1d314ee9dddd51a5ce88cd4ae48', "aria-hidden": "true", icon: danger, size: "small" }), this.errorMessage))
105
105
  ];
106
106
  const hostClass = this.classNames();
107
107
  this.hostKeeper.record(hostClass);
108
- return (h(Host, { key: '374003032b28407581a55f1fb0a304c49acf3c16', class: hostClass }, this.hasImage() && (h("div", { key: '7eeb516267de1afcfa85fc7567999db488c2f4f5', class: "pds-radio__image-container", part: "image-container" }, h("slot", { key: 'a312fea06f0c015bffab3bb392ff04d4913fd1c0', name: "image", onSlotchange: () => (this._hasImage = this.hasImageSlot()) }))), this.hasImage() ? (h("div", { class: "pds-radio__content-wrapper" }, renderLabelAndMessages())) : (renderLabelAndMessages())));
108
+ return (h(Host, { key: '351376552abee90f50c3aceb03ecf8a81ae1b82b', class: hostClass }, this.hasImage() && (h("div", { key: 'fd85fd36b2ebd99b12c3bb2a13cf3d3e3c26530f', class: "pds-radio__image-container", part: "image-container" }, h("slot", { key: 'abe75dbe38d29340b805f70861b084c1b3836be9', name: "image", onSlotchange: () => (this._hasImage = this.hasImageSlot()) }))), this.hasImage() ? (h("div", { class: "pds-radio__content-wrapper" }, renderLabelAndMessages())) : (renderLabelAndMessages())));
109
109
  }
110
110
  static get is() { return "pds-radio"; }
111
111
  static get encapsulation() { return "scoped"; }
@@ -167,7 +167,7 @@ export class PdsRadioGroup {
167
167
  'pds-radio-group__radios': true,
168
168
  'pds-radio-group__radios--row': this.direction === 'row',
169
169
  'pds-radio-group__radios--column': this.direction === 'column',
170
- } }, h("slot", { key: 'd88dde3c631e25c01bc7b6d3ef5d517255fabd1a', onSlotchange: () => this.updateChildRadios() })), this.helperMessage && (h("div", { key: 'a229214ed01663b0fc34f35e27c7148b4eb412ab', class: "pds-radio-group__message", id: messageId(this._groupId, 'helper') }, this.helperMessage)), this.errorMessage && (h("div", { key: 'dc092f2e6dbef0cb369cf00a6eccf9a095a0313d', class: "pds-radio-group__message pds-radio-group__message--error", id: messageId(this._groupId, 'error'), "aria-live": "assertive" }, h("pds-icon", { key: 'd242da328731e55fa52d8e624fdfc750bfa6cce4', icon: danger, size: "small" }), this.errorMessage))));
170
+ } }, h("slot", { key: 'd88dde3c631e25c01bc7b6d3ef5d517255fabd1a', onSlotchange: () => this.updateChildRadios() })), this.helperMessage && (h("div", { key: 'a229214ed01663b0fc34f35e27c7148b4eb412ab', class: "pds-radio-group__message", id: messageId(this._groupId, 'helper') }, this.helperMessage)), this.errorMessage && (h("div", { key: 'dc092f2e6dbef0cb369cf00a6eccf9a095a0313d', class: "pds-radio-group__message pds-radio-group__message--error", id: messageId(this._groupId, 'error'), "aria-live": "assertive" }, h("pds-icon", { key: '83b0129d3d45b74ef1f53acc923387b2de7f079b', "aria-hidden": "true", icon: danger, size: "small" }), this.errorMessage))));
171
171
  }
172
172
  static get is() { return "pds-radio-group"; }
173
173
  static get encapsulation() { return "scoped"; }
@@ -115,7 +115,7 @@ export class PdsSwitch {
115
115
  this.hostKeeper.record(hostClass, {}, { 'aria-disabled': ariaDisabled });
116
116
  return (h(Host, { key: 'b8bd2870be101081aa8a65cdee45397bc4c21435', class: hostClass, "aria-disabled": ariaDisabled }, h("label", { key: '1e223f22f7fc6c576098353ad67dc11500def8b4', htmlFor: this.componentId }, h("input", Object.assign({ key: 'edf59869e165a65b97465ea9de06f7ea82fbc9a6', "aria-describedby": assignDescription(this.componentId, this.invalid, this.helperMessage, this.errorMessage), "aria-invalid": this.invalid ? "true" : undefined, checked: this.checked, class: "pds-switch__input", disabled: this.disabled, id: this.componentId, name: this.name ? this.name : this.componentId, onChange: this.onSwitchUpdate, required: this.required, type: "checkbox", value: this.value }, this.inheritedAttributes)), h("span", { key: '1c252b9455471069f8c6ea6c13f2e88c99308b41', class: this.hideLabel ? 'visually-hidden' : '' }, h("slot", { key: 'ce0d471918bd3018f5e55d0d1e6c5c2247790f81', name: "label", onSlotchange: this.updateHasLabelSlot }), !this.hasLabelSlot && this.label)), this.helperMessage &&
117
117
  h("div", { key: '64874b15403f1f42344715ac34febdfb8d94042a', class: `pds-switch__message`, id: messageId(this.componentId, 'helper') }, this.helperMessage), this.errorMessage &&
118
- h("div", { key: '560ea486859124bb0ac2f75ab4bd34b0bca7efbc', class: `pds-switch__message pds-switch__message--error`, id: messageId(this.componentId, 'error'), "aria-live": "assertive" }, h("pds-icon", { key: 'f49caaba976579eb5588e442499595c7dba7ceff', icon: danger, size: "small" }), this.errorMessage)));
118
+ h("div", { key: '560ea486859124bb0ac2f75ab4bd34b0bca7efbc', class: `pds-switch__message pds-switch__message--error`, id: messageId(this.componentId, 'error'), "aria-live": "assertive" }, h("pds-icon", { key: '2ae8dc876745af26fa2418f2265248aeb8f97a2b', "aria-hidden": "true", icon: danger, size: "small" }), this.errorMessage)));
119
119
  }
120
120
  static get is() { return "pds-switch"; }
121
121
  static get encapsulation() { return "shadow"; }
@@ -203,7 +203,7 @@ export class PdsTableHeadCell {
203
203
  this.tableRef.fixedColumn &&
204
204
  this.tableRef.selectable
205
205
  ? { '--fixed-cell-position': '40px' }
206
- : {} }, h("slot", { key: '96084d87a34d7b601a8e8325f344f1fe6601204e' }), this.sortable && this.isActive && (h("pds-icon", { key: 'cba838fa029b280561676c2b57af7f8f00055511', icon: this.sortingDirection === 'asc' ? upSmall : downSmall, part: "sort-icon" }))));
206
+ : {} }, h("slot", { key: '96084d87a34d7b601a8e8325f344f1fe6601204e' }), this.sortable && this.isActive && (h("pds-icon", { key: '79606519d810dc9882a91cbc3b6a199457086631', icon: this.sortingDirection === 'asc' ? upSmall : downSmall, part: "sort-icon", "aria-hidden": "true" }))));
207
207
  }
208
208
  static get is() { return "pds-table-head-cell"; }
209
209
  static get encapsulation() { return "shadow"; }
@@ -488,7 +488,7 @@ const TruncateTemplate = (args) => html`
488
488
  </pds-table-row>
489
489
  </pds-table-body>
490
490
  </pds-table>
491
- <pds-text tag="p" size="xs" color="secondary" gutter="sm">Hover over truncated cells to see the full content in a tooltip.</pds-text>`;
491
+ <pds-text tag="p" size="xs" color="secondary" gutter="sm">Hover over or tab to a truncated cell to see the full content in a tooltip.</pds-text>`;
492
492
 
493
493
  export const Truncate = TruncateTemplate.bind();
494
494
  Truncate.args = {
@@ -113,7 +113,7 @@ const TruncateTemplate = (args) => html`
113
113
  ${args.slot}
114
114
  </pds-text>
115
115
  </div>
116
- <pds-text tag="p" size="xs" color="secondary" gutter="sm">Hover over the truncated text above to see the full content in a tooltip.</pds-text>`;
116
+ <pds-text tag="p" size="xs" color="secondary" gutter="sm">Hover over or tab to the truncated text above to see the full content in a tooltip.</pds-text>`;
117
117
 
118
118
  export const Truncate = TruncateTemplate.bind();
119
119
  Truncate.args = {
@@ -309,7 +309,7 @@ export class PdsTextarea {
309
309
  return (h(Host, { key: '5933dab0c3cd25b1cf2f6851ece8e8064bc7f3b9', "aria-disabled": this.disabled ? 'true' : null, "aria-readonly": this.readonly ? 'true' : null, "has-action": this.hasAction && !this.hideLabel ? 'true' : null }, h("div", { key: '4f1752514d8e5b0f0e3439515cb9e5f043607da1', class: "pds-textarea" }, this.label &&
310
310
  h("div", { key: 'd4e6df0b381d781b70eeae72ff2f0e9428c0d901', class: "pds-textarea__label-wrapper" }, h("label", { key: '747d82067eb571d02840cfd981865821099f288b', htmlFor: this.componentId }, h("span", { key: 'bfab6e8ede66d0169dd020b7dd97e125aba417b0', class: this.hideLabel ? 'visually-hidden' : '' }, this.label)), !this.hideLabel && this.renderAction()), h("div", { key: 'a18611c03270e7491c1af69f931419c832192c06', class: "pds-textarea__field-wrapper" }, h("textarea", Object.assign({ key: '868382c7b04a4e59c0daa9d98afcdc9048f6224f', ref: (el) => this.nativeTextarea = el, "aria-describedby": assignDescription(this.componentId, this.invalid, this.helperMessage, this.errorMessage), "aria-invalid": this.invalid ? "true" : undefined, autocomplete: this.autocomplete, class: this.textareaClassNames(), disabled: this.disabled, id: this.componentId, maxlength: this.maxLength, name: this.name, placeholder: this.placeholder, readOnly: this.readonly, required: this.required, rows: this.rows, onBlur: this.onBlur, onChange: this.onTextareaChange, onFocus: this.onFocus, onInput: this.onInput, onKeyDown: this.onKeyDown }, this.inheritedAttributes), value), this.renderCharacterCounter()), this.helperMessage &&
311
311
  h("p", { key: 'dcb36f21443d2a782c25b7c29c4ce6215f3b212b', class: "pds-textarea__helper-message", id: messageId(this.componentId, 'helper') }, this.helperMessage), this.invalid &&
312
- h("p", { key: 'be30fb1a38453296e26b775955389c0ec569df67', "aria-live": "assertive", class: "pds-textarea__error-message", id: messageId(this.componentId, 'error') }, h("pds-icon", { key: 'd93dce10d9d9db5815ac5f331848940aed735ba0', icon: danger, size: "small" }), this.errorMessage))));
312
+ h("p", { key: 'be30fb1a38453296e26b775955389c0ec569df67', "aria-live": "assertive", class: "pds-textarea__error-message", id: messageId(this.componentId, 'error') }, h("pds-icon", { key: '853f06489ef6ca50205799659d221bd3bea7d8da', "aria-hidden": "true", icon: danger, size: "small" }), this.errorMessage))));
313
313
  }
314
314
  static get is() { return "pds-textarea"; }
315
315
  static get encapsulation() { return "shadow"; }
@@ -127,7 +127,7 @@ export class PdsToast {
127
127
  'pds-toast--animating-out': this.isAnimatingOut
128
128
  }, role: "alert", "aria-live": "polite" }, this.renderIcon(), h("span", { key: 'eaf615d41c138e2c73088d29bac9349f49a63ba7', class: "pds-toast__message" }, h("slot", { key: '33973447154d80b016f5a7954b0473db03228379' })), this.dismissible && (h("button", { key: 'e189834633e1e648282ee43633fd05e53ce38869', type: "button", part: "dismiss", class: "pds-toast__button", onClick: () => {
129
129
  this.dismiss();
130
- }, "aria-label": this.dismissLabel }, h("pds-icon", { key: '623a0e4d56e30781fb31bcd085511c17a4777edc', name: "remove" }))))));
130
+ }, "aria-label": this.dismissLabel }, h("pds-icon", { key: '913201979e52be743ba933ff173e023b81553362', name: "remove", "aria-hidden": "true" }))))));
131
131
  }
132
132
  static get is() { return "pds-toast"; }
133
133
  static get encapsulation() { return "shadow"; }
@@ -57,12 +57,24 @@ function injectStyles() {
57
57
  function isOverflowing(el) {
58
58
  return el.scrollWidth > el.clientWidth;
59
59
  }
60
+ /**
61
+ * Engines without `:focus-visible` throw on the selector, so there every
62
+ * focus counts as visible rather than none.
63
+ */
64
+ function isFocusVisible(el) {
65
+ try {
66
+ return el.matches(':focus-visible');
67
+ }
68
+ catch (_a) {
69
+ return true;
70
+ }
71
+ }
60
72
  /**
61
73
  * Sets up automatic tooltip behavior for a truncated element.
62
74
  *
63
- * When the element's text overflows, hovering or focusing the host element
64
- * will display a tooltip showing the full text content. The tooltip is
65
- * rendered as a portal on document.body and positioned with Floating UI.
75
+ * When the element's text overflows, hovering the host element or giving it
76
+ * keyboard focus will display a tooltip showing the full text content. The
77
+ * tooltip is rendered as a portal on document.body and positioned with Floating UI.
66
78
  *
67
79
  * Also owns the element's `tabindex` while it is clipped — see syncTabIndex.
68
80
  *
@@ -253,18 +265,30 @@ export function setupTruncationTooltip(options) {
253
265
  isHoveringPortal = false;
254
266
  scheduleHide();
255
267
  }
256
- function handleFocusIn() {
268
+ // Keyboard focus only: focus from a click would pin the tooltip open after the pointer leaves.
269
+ // Events from inside a shadow root arrive retargeted to its host, so test the innermost target.
270
+ function showForVisibleFocus(event) {
271
+ if (!isFocusVisible(event.composedPath()[0]))
272
+ return;
257
273
  isFocused = true;
258
274
  showTooltip();
259
275
  }
276
+ // A key press after a click makes focus visible in place, with no new focusin. keyup, not
277
+ // keydown, so Tab's focus move lands first; Escape asks to dismiss, never to reveal.
278
+ function handleKeyUp(event) {
279
+ if (isFocused || event.key === 'Escape')
280
+ return;
281
+ showForVisibleFocus(event);
282
+ }
260
283
  function handleFocusOut() {
261
284
  isFocused = false;
262
285
  scheduleHide();
263
286
  }
264
287
  hostEl.addEventListener('mouseenter', handleMouseEnter);
265
288
  hostEl.addEventListener('mouseleave', handleMouseLeave);
266
- focusEl.addEventListener('focusin', handleFocusIn);
289
+ focusEl.addEventListener('focusin', showForVisibleFocus);
267
290
  focusEl.addEventListener('focusout', handleFocusOut);
291
+ focusEl.addEventListener('keyup', handleKeyUp);
268
292
  // Return cleanup function
269
293
  return () => {
270
294
  disposed = true;
@@ -284,8 +308,9 @@ export function setupTruncationTooltip(options) {
284
308
  }
285
309
  hostEl.removeEventListener('mouseenter', handleMouseEnter);
286
310
  hostEl.removeEventListener('mouseleave', handleMouseLeave);
287
- focusEl.removeEventListener('focusin', handleFocusIn);
311
+ focusEl.removeEventListener('focusin', showForVisibleFocus);
288
312
  focusEl.removeEventListener('focusout', handleFocusOut);
313
+ focusEl.removeEventListener('keyup', handleKeyUp);
289
314
  removePortal();
290
315
  };
291
316
  }