polaris_view_components 0.10.1 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -62
  3. data/app/assets/javascripts/polaris_view_components/autocomplete_controller.js +9 -3
  4. data/app/assets/javascripts/polaris_view_components/dropzone_controller.js +18 -8
  5. data/app/assets/javascripts/polaris_view_components/popover_controller.js +39 -13
  6. data/app/assets/javascripts/polaris_view_components.js +112 -59
  7. data/app/assets/stylesheets/polaris_view_components/custom.css +27 -8
  8. data/app/assets/stylesheets/polaris_view_components.css +21 -9
  9. data/app/components/polaris/autocomplete_component.html.erb +3 -0
  10. data/app/components/polaris/autocomplete_component.rb +3 -0
  11. data/app/components/polaris/button_component.html.erb +2 -0
  12. data/app/components/polaris/card/header_component.html.erb +4 -2
  13. data/app/components/polaris/data_table/column_component.rb +2 -1
  14. data/app/components/polaris/data_table_component.html.erb +6 -2
  15. data/app/components/polaris/data_table_component.rb +10 -0
  16. data/app/components/polaris/dropzone_component.html.erb +38 -32
  17. data/app/components/polaris/dropzone_component.rb +4 -1
  18. data/app/components/polaris/empty_search_results_component.html.erb +15 -0
  19. data/app/components/polaris/empty_search_results_component.rb +21 -0
  20. data/app/components/polaris/headless_button.html.erb +2 -0
  21. data/app/components/polaris/headless_button.rb +1 -1
  22. data/app/components/polaris/index_table/column_component.rb +2 -1
  23. data/app/components/polaris/index_table_component.html.erb +3 -5
  24. data/app/components/polaris/index_table_component.rb +10 -0
  25. data/app/components/polaris/page_component.html.erb +4 -4
  26. data/app/components/polaris/page_component.rb +7 -2
  27. data/app/components/polaris/popover_component.html.erb +26 -16
  28. data/app/components/polaris/popover_component.rb +6 -1
  29. data/app/components/polaris/resource_item/shortcut_actions_component.html.erb +21 -0
  30. data/app/components/polaris/resource_item/shortcut_actions_component.rb +88 -0
  31. data/app/components/polaris/resource_item_component.html.erb +3 -0
  32. data/app/components/polaris/resource_item_component.rb +11 -1
  33. data/app/components/polaris/resource_list_component.html.erb +11 -0
  34. data/app/components/polaris/resource_list_component.rb +21 -0
  35. data/app/components/polaris/skeleton_page_component.html.erb +23 -0
  36. data/app/components/polaris/skeleton_page_component.rb +22 -0
  37. data/app/helpers/polaris/url_helper.rb +37 -0
  38. data/app/helpers/polaris/view_helper.rb +2 -0
  39. data/lib/install/install.rb +57 -0
  40. data/lib/polaris/view_components/version.rb +1 -1
  41. data/lib/tasks/polaris_view_components_tasks.rake +6 -0
  42. metadata +10 -6
  43. data/lib/generators/polaris_view_components/USAGE +0 -5
  44. data/lib/generators/polaris_view_components/install_generator.rb +0 -35
  45. data/lib/generators/polaris_view_components/templates/README +0 -14
  46. data/lib/generators/polaris_view_components/templates/stimulus_index.js +0 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6559d19f7338ce9195fa1903e58f14596006d813bbc30599061bce4c00d49c9d
4
- data.tar.gz: 61223fea9beddaa96b4762b8b7dc00cf42051a462e92bab25733d6f35b232ae2
3
+ metadata.gz: c6ac82b9541cc182ed79f45d4850e23a0afe6255e3b94d0071c2cc9716dc81e8
4
+ data.tar.gz: 48de38d93d9d972eb4becc326242ea894ea56532f68e0527339ac0647ee662f1
5
5
  SHA512:
6
- metadata.gz: 8ad6e0a1dc0f3f491e6beeae7dc4c6c72a8a077ec0b6f6c4cc04e95723761aa93181d663ef9f9f52945d5fad3be40a9d3a3b7e3f64504f9e347925b733841fb5
7
- data.tar.gz: 4b5b661dbd74e991dde7e7386f33b65caeaac53839b0c3c0d3951acb30f5179d1d007d97a08c058024c7e1a4e72d56fba8ba0d0a12729b885ebecf458a4116b3
6
+ metadata.gz: 20b0f280d097398cf7793b35ddf77d8dfcdafea0039dd5114e1261fafa62d7bad528b1e3b83ac0015dce4af3619459f711b16bbeb85429f428d5766b1457b82d
7
+ data.tar.gz: 5d187b4045398761dc1dfd1052965220ee80888a5ef17a3cef8eba19e6632ecf633497275f0833bd36c1e8ff0e0cb22d1fd11a8f0639ff0d2ba4a70a9881da88
data/README.md CHANGED
@@ -20,95 +20,57 @@ Render Polaris ViewComponents:
20
20
  <% end %>
21
21
  ```
22
22
 
23
+ ## Dependencies
24
+
25
+ - [Stimulus](https://stimulus.hotwired.dev/)
26
+
23
27
  ## Installation
24
28
 
25
- In `Gemfile`, add:
29
+ Add to `Gemfile`:
26
30
 
27
31
  ```ruby
28
- gem 'polaris_view_components'
32
+ gem "polaris_view_components"
29
33
  ```
30
34
 
31
- Run install generator:
35
+ Run installer:
32
36
  ```bash
33
- rails generate polaris_view_components:install
37
+ bin/rails polaris_view_components:install
34
38
  ```
35
39
 
36
- Setup Polaris styles in your layouts `<head>` tag:
37
-
38
- ```erb
39
- <%= stylesheet_link_tag 'polaris_view_components' %>
40
- ```
41
-
42
- Define Polaris style on your `<body>` tag:
43
-
44
- ```erb
45
- <body style="<%= polaris_body_styles %>">
46
- ```
40
+ ## Development
47
41
 
48
- ### Importmaps
42
+ To get started:
49
43
 
50
- Install dependencies:
51
- ```
52
- bin/importmap pin @rails/request.js --download
53
- ```
44
+ 1. Run: `bundle install`
45
+ 1. Run: `yarn install`
46
+ 1. Run: `bin/dev`
54
47
 
55
- If you use sprockets make sure the vendor folder is loaded in `app/assets/config/manifest.js`:
56
- ```js
57
- //= link_tree ../../../vendor/assets/javascripts .js
58
- ```
48
+ It will open demo app with component previews on `localhost:4000`. You can change components and they will be updated on page reload. Component previews located in `demo/test/components/previews`.
59
49
 
60
- Add to `config/importmap.rb`:
50
+ To run tests:
61
51
 
62
- ```rb
63
- pin "polaris-view-components", to: "polaris_view_components.js"
52
+ ```bash
53
+ rake
64
54
  ```
65
55
 
66
- Add to `app/javascript/controllers/index.js`:
67
- ```javascript
68
- // ...
56
+ ## Releases
69
57
 
70
- import { registerPolarisControllers } from "polaris-view-components"
71
- registerPolarisControllers(Stimulus)
72
- ```
73
-
74
- ### NPM
58
+ The library follows [semantic versioning](https://semver.org/). To draft a new release you need to run `script/release` with a new version number:
75
59
 
76
- Install NPM package:
77
60
  ```bash
78
- yarn add polaris-view-components @rails/request.js
79
- ```
80
-
81
- Add to `app/javascript/controllers/index.js`:
82
- ```javascript
83
- // ...
84
-
85
- import { registerPolarisControllers } from "polaris-view-components"
86
- registerPolarisControllers(Stimulus)
61
+ script/release VERSION
87
62
  ```
88
63
 
89
- ## Dependencies
90
-
91
- In addition to the dependencies declared in the `gemspec`, Polaris ViewComponents assumes the presence of Polaris CSS.
92
-
93
- ## Development
94
-
95
- To get started:
64
+ Where the VERSION is the version number you want to release. This script will update the version in the gem and push it to GitHub and Rubygems automatically.
96
65
 
97
- 1. Run: `bundle install`
98
- 2. Run: `bin/dev`
99
-
100
- It will open demo app with component previews on `localhost:4000`. You can change components and they will be updated on page reload. Component previews located in `demo/test/components/previews`.
66
+ To release a new version of npm package update the package.json file with the new version number and run:
101
67
 
102
- To release gem run:
103
- ```bash
104
- script/release
105
- ```
106
-
107
- To release npm package run:
108
68
  ```bash
109
69
  npm run release
110
70
  ```
111
71
 
72
+ After that make sure to commit changes in package.json.
73
+
112
74
  ## License
113
75
 
114
76
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -3,8 +3,8 @@ import { get } from '@rails/request.js'
3
3
  import { debounce } from './utils'
4
4
 
5
5
  export default class extends Controller {
6
- static targets = ['popover', 'input', 'results', 'option', 'emptyState']
7
- static values = { url: String, selected: Array }
6
+ static targets = ['popover', 'input', 'hiddenInput', 'results', 'option', 'emptyState']
7
+ static values = { multiple: Boolean, url: String, selected: Array }
8
8
 
9
9
  connect() {
10
10
  this.inputTarget.addEventListener("input", this.onInputChange)
@@ -32,6 +32,12 @@ export default class extends Controller {
32
32
  })
33
33
 
34
34
  this.element.dispatchEvent(changeEvent)
35
+
36
+ if (!this.multipleValue) {
37
+ this.popoverController.forceHide()
38
+ this.inputTarget.value = label
39
+ this.hiddenInputTarget.value = input.value
40
+ }
35
41
  }
36
42
 
37
43
  onInputChange = debounce(() => {
@@ -65,7 +71,7 @@ export default class extends Controller {
65
71
  }
66
72
 
67
73
  get visibleOptions() {
68
- return this.optionTargets.filter(option => {
74
+ return [...this.optionTargets].filter(option => {
69
75
  return !option.classList.contains('Polaris--hidden')
70
76
  })
71
77
  }
@@ -30,7 +30,8 @@ export default class extends Controller {
30
30
  dropOnPage: Boolean,
31
31
  focused: Boolean,
32
32
  renderPreview: Boolean,
33
- size: String
33
+ size: String,
34
+ removePreviewsAfterUpload: Boolean
34
35
  }
35
36
 
36
37
  files = []
@@ -164,11 +165,12 @@ export default class extends Controller {
164
165
 
165
166
  onDirectUploadsEnd = () => {
166
167
  this.enable()
167
- this.clearFiles()
168
+ this.clearFiles(this.removePreviewsAfterUploadValue)
168
169
 
169
170
  if (this.acceptedFiles.length === 0) return
170
171
 
171
- this.loaderTarget.classList.remove("Polaris--hidden")
172
+ if (this.hasLoaderTarget)
173
+ this.loaderTarget.classList.remove("Polaris--hidden")
172
174
  }
173
175
 
174
176
  onDirectUploadInitialize = (event) => {
@@ -179,12 +181,15 @@ export default class extends Controller {
179
181
  if (this.acceptedFiles.length === 0) return
180
182
 
181
183
  if (this.sizeValue == 'small') {
182
- this.clearFiles()
183
- this.loaderTarget.classList.remove("Polaris--hidden")
184
+ this.removePreview()
185
+ if (this.hasLoaderTarget)
186
+ this.loaderTarget.classList.remove("Polaris--hidden")
184
187
  } else {
185
188
  const content = dropzone.querySelector(`[data-file-name="${file.name}"]`)
186
- const progressBar = content.parentElement.querySelector('[data-target="progress-bar"]')
187
- progressBar.id = `direct-upload-${id}`
189
+ if (content) {
190
+ const progressBar = content.parentElement.querySelector('[data-target="progress-bar"]')
191
+ progressBar.id = `direct-upload-${id}`
192
+ }
188
193
  }
189
194
  }
190
195
 
@@ -354,13 +359,18 @@ export default class extends Controller {
354
359
  return clone
355
360
  }
356
361
 
357
- clearFiles () {
362
+ clearFiles (removePreview = true) {
358
363
  if (!this.previewRendered) return
359
364
 
360
365
  this.acceptedFiles = []
361
366
  this.files = []
362
367
  this.rejectedFiles = []
363
368
 
369
+ if (removePreview)
370
+ this.removePreview()
371
+ }
372
+
373
+ removePreview () {
364
374
  if (!this.hasPreviewTarget) return
365
375
 
366
376
  this.previewTarget.remove()
@@ -2,15 +2,16 @@ import { Controller } from "@hotwired/stimulus"
2
2
  import { createPopper } from "@popperjs/core/dist/esm"
3
3
 
4
4
  export default class extends Controller {
5
- static targets = ["activator", "popover"]
5
+ static targets = ["activator", "popover", "template"]
6
6
  static classes = ["open", "closed"]
7
7
  static values = {
8
+ appendToBody: Boolean,
8
9
  placement: String,
9
10
  active: Boolean
10
11
  }
11
12
 
12
13
  connect() {
13
- this.popper = createPopper(this.activatorTarget, this.popoverTarget, {
14
+ const popperOptions = {
14
15
  placement: this.placementValue,
15
16
  modifiers: [
16
17
  {
@@ -26,31 +27,56 @@ export default class extends Controller {
26
27
  },
27
28
  }
28
29
  ]
29
- })
30
+ }
31
+
32
+ if (this.appendToBodyValue) {
33
+ const clonedTemplate = this.templateTarget.content.cloneNode(true)
34
+ this.target = clonedTemplate.firstElementChild
35
+ popperOptions['strategy'] = 'fixed'
36
+
37
+ document.body.appendChild(clonedTemplate)
38
+ }
39
+
40
+ this.popper = createPopper(this.activatorTarget, this.target, popperOptions)
30
41
  if (this.activeValue) {
31
42
  this.show()
32
43
  }
33
44
  }
34
45
 
35
- toggle() {
36
- this.popoverTarget.classList.toggle(this.closedClass)
37
- this.popoverTarget.classList.toggle(this.openClass)
46
+ async toggle() {
47
+ this.target.classList.toggle(this.closedClass)
48
+ this.target.classList.toggle(this.openClass)
49
+ await this.popper.update()
38
50
  }
39
51
 
40
52
  async show() {
41
- this.popoverTarget.classList.remove(this.closedClass)
42
- this.popoverTarget.classList.add(this.openClass)
53
+ this.target.classList.remove(this.closedClass)
54
+ this.target.classList.add(this.openClass)
43
55
  await this.popper.update()
44
56
  }
45
57
 
46
58
  hide(event) {
47
- if (!this.element.contains(event.target) && !this.popoverTarget.classList.contains(this.closedClass)) {
48
- this.forceHide()
49
- }
59
+ if (this.element.contains(event.target)) return
60
+ if (this.target.classList.contains(this.closedClass)) return
61
+ if (this.appendToBodyValue && this.target.contains(event.target)) return
62
+
63
+ this.forceHide()
50
64
  }
51
65
 
52
66
  forceHide() {
53
- this.popoverTarget.classList.remove(this.openClass)
54
- this.popoverTarget.classList.add(this.closedClass)
67
+ this.target.classList.remove(this.openClass)
68
+ this.target.classList.add(this.closedClass)
69
+ }
70
+
71
+ get target() {
72
+ if (this.hasPopoverTarget) {
73
+ return this.popoverTarget
74
+ } else {
75
+ return this._target
76
+ }
77
+ }
78
+
79
+ set target(value) {
80
+ this._target = value
55
81
  }
56
82
  }
@@ -174,8 +174,9 @@ function formatBytes(bytes, decimals) {
174
174
  }
175
175
 
176
176
  class Autocomplete extends Controller {
177
- static targets=[ "popover", "input", "results", "option", "emptyState" ];
177
+ static targets=[ "popover", "input", "hiddenInput", "results", "option", "emptyState" ];
178
178
  static values={
179
+ multiple: Boolean,
179
180
  url: String,
180
181
  selected: Array
181
182
  };
@@ -203,6 +204,11 @@ class Autocomplete extends Controller {
203
204
  }
204
205
  });
205
206
  this.element.dispatchEvent(changeEvent);
207
+ if (!this.multipleValue) {
208
+ this.popoverController.forceHide();
209
+ this.inputTarget.value = label;
210
+ this.hiddenInputTarget.value = input.value;
211
+ }
206
212
  }
207
213
  onInputChange=debounce$1((() => {
208
214
  if (this.isRemote) {
@@ -228,7 +234,7 @@ class Autocomplete extends Controller {
228
234
  return this.inputTarget.value;
229
235
  }
230
236
  get visibleOptions() {
231
- return this.optionTargets.filter((option => !option.classList.contains("Polaris--hidden")));
237
+ return [ ...this.optionTargets ].filter((option => !option.classList.contains("Polaris--hidden")));
232
238
  }
233
239
  handleResults() {
234
240
  if (this.visibleOptions.length > 0) {
@@ -358,7 +364,8 @@ class Dropzone extends Controller {
358
364
  dropOnPage: Boolean,
359
365
  focused: Boolean,
360
366
  renderPreview: Boolean,
361
- size: String
367
+ size: String,
368
+ removePreviewsAfterUpload: Boolean
362
369
  };
363
370
  files=[];
364
371
  rejectedFiles=[];
@@ -458,9 +465,9 @@ class Dropzone extends Controller {
458
465
  };
459
466
  onDirectUploadsEnd=() => {
460
467
  this.enable();
461
- this.clearFiles();
468
+ this.clearFiles(this.removePreviewsAfterUploadValue);
462
469
  if (this.acceptedFiles.length === 0) return;
463
- this.loaderTarget.classList.remove("Polaris--hidden");
470
+ if (this.hasLoaderTarget) this.loaderTarget.classList.remove("Polaris--hidden");
464
471
  };
465
472
  onDirectUploadInitialize=event => {
466
473
  const {target: target, detail: detail} = event;
@@ -469,12 +476,14 @@ class Dropzone extends Controller {
469
476
  if (!dropzone) return;
470
477
  if (this.acceptedFiles.length === 0) return;
471
478
  if (this.sizeValue == "small") {
472
- this.clearFiles();
473
- this.loaderTarget.classList.remove("Polaris--hidden");
479
+ this.removePreview();
480
+ if (this.hasLoaderTarget) this.loaderTarget.classList.remove("Polaris--hidden");
474
481
  } else {
475
482
  const content = dropzone.querySelector(`[data-file-name="${file.name}"]`);
476
- const progressBar = content.parentElement.querySelector('[data-target="progress-bar"]');
477
- progressBar.id = `direct-upload-${id}`;
483
+ if (content) {
484
+ const progressBar = content.parentElement.querySelector('[data-target="progress-bar"]');
485
+ progressBar.id = `direct-upload-${id}`;
486
+ }
478
487
  }
479
488
  };
480
489
  onDirectUploadStart=event => {
@@ -612,11 +621,14 @@ class Dropzone extends Controller {
612
621
  }
613
622
  return clone;
614
623
  }
615
- clearFiles() {
624
+ clearFiles(removePreview = true) {
616
625
  if (!this.previewRendered) return;
617
626
  this.acceptedFiles = [];
618
627
  this.files = [];
619
628
  this.rejectedFiles = [];
629
+ if (removePreview) this.removePreview();
630
+ }
631
+ removePreview() {
620
632
  if (!this.hasPreviewTarget) return;
621
633
  this.previewTarget.remove();
622
634
  this.previewRendered = false;
@@ -1014,32 +1026,49 @@ var min = Math.min;
1014
1026
 
1015
1027
  var round = Math.round;
1016
1028
 
1017
- function getBoundingClientRect(element, includeScale) {
1029
+ function getUAString() {
1030
+ var uaData = navigator.userAgentData;
1031
+ if (uaData != null && uaData.brands) {
1032
+ return uaData.brands.map((function(item) {
1033
+ return item.brand + "/" + item.version;
1034
+ })).join(" ");
1035
+ }
1036
+ return navigator.userAgent;
1037
+ }
1038
+
1039
+ function isLayoutViewport() {
1040
+ return !/^((?!chrome|android).)*safari/i.test(getUAString());
1041
+ }
1042
+
1043
+ function getBoundingClientRect(element, includeScale, isFixedStrategy) {
1018
1044
  if (includeScale === void 0) {
1019
1045
  includeScale = false;
1020
1046
  }
1021
- var rect = element.getBoundingClientRect();
1047
+ if (isFixedStrategy === void 0) {
1048
+ isFixedStrategy = false;
1049
+ }
1050
+ var clientRect = element.getBoundingClientRect();
1022
1051
  var scaleX = 1;
1023
1052
  var scaleY = 1;
1024
- if (isHTMLElement(element) && includeScale) {
1025
- var offsetHeight = element.offsetHeight;
1026
- var offsetWidth = element.offsetWidth;
1027
- if (offsetWidth > 0) {
1028
- scaleX = round(rect.width) / offsetWidth || 1;
1029
- }
1030
- if (offsetHeight > 0) {
1031
- scaleY = round(rect.height) / offsetHeight || 1;
1032
- }
1033
- }
1053
+ if (includeScale && isHTMLElement(element)) {
1054
+ scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1;
1055
+ scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1;
1056
+ }
1057
+ var _ref = isElement(element) ? getWindow(element) : window, visualViewport = _ref.visualViewport;
1058
+ var addVisualOffsets = !isLayoutViewport() && isFixedStrategy;
1059
+ var x = (clientRect.left + (addVisualOffsets && visualViewport ? visualViewport.offsetLeft : 0)) / scaleX;
1060
+ var y = (clientRect.top + (addVisualOffsets && visualViewport ? visualViewport.offsetTop : 0)) / scaleY;
1061
+ var width = clientRect.width / scaleX;
1062
+ var height = clientRect.height / scaleY;
1034
1063
  return {
1035
- width: rect.width / scaleX,
1036
- height: rect.height / scaleY,
1037
- top: rect.top / scaleY,
1038
- right: rect.right / scaleX,
1039
- bottom: rect.bottom / scaleY,
1040
- left: rect.left / scaleX,
1041
- x: rect.left / scaleX,
1042
- y: rect.top / scaleY
1064
+ width: width,
1065
+ height: height,
1066
+ top: y,
1067
+ right: x + width,
1068
+ bottom: y + height,
1069
+ left: x,
1070
+ x: x,
1071
+ y: y
1043
1072
  };
1044
1073
  }
1045
1074
 
@@ -1104,8 +1133,8 @@ function getTrueOffsetParent(element) {
1104
1133
  }
1105
1134
 
1106
1135
  function getContainingBlock(element) {
1107
- var isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") !== -1;
1108
- var isIE = navigator.userAgent.indexOf("Trident") !== -1;
1136
+ var isFirefox = /firefox/i.test(getUAString());
1137
+ var isIE = /Trident/i.test(getUAString());
1109
1138
  if (isIE && isHTMLElement(element)) {
1110
1139
  var elementCss = getComputedStyle$1(element);
1111
1140
  if (elementCss.position === "fixed") {
@@ -1113,6 +1142,9 @@ function getContainingBlock(element) {
1113
1142
  }
1114
1143
  }
1115
1144
  var currentNode = getParentNode(element);
1145
+ if (isShadowRoot(currentNode)) {
1146
+ currentNode = currentNode.host;
1147
+ }
1116
1148
  while (isHTMLElement(currentNode) && [ "html", "body" ].indexOf(getNodeName(currentNode)) < 0) {
1117
1149
  var css = getComputedStyle$1(currentNode);
1118
1150
  if (css.transform !== "none" || css.perspective !== "none" || css.contain === "paint" || [ "transform", "perspective" ].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === "filter" || isFirefox && css.filter && css.filter !== "none") {
@@ -1287,13 +1319,13 @@ function mapToStyles(_ref2) {
1287
1319
  offsetParent = offsetParent;
1288
1320
  if (placement === top || (placement === left || placement === right) && variation === end) {
1289
1321
  sideY = bottom;
1290
- var offsetY = isFixed && win.visualViewport ? win.visualViewport.height : offsetParent[heightProp];
1322
+ var offsetY = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.height : offsetParent[heightProp];
1291
1323
  y -= offsetY - popperRect.height;
1292
1324
  y *= gpuAcceleration ? 1 : -1;
1293
1325
  }
1294
1326
  if (placement === left || (placement === top || placement === bottom) && variation === end) {
1295
1327
  sideX = right;
1296
- var offsetX = isFixed && win.visualViewport ? win.visualViewport.width : offsetParent[widthProp];
1328
+ var offsetX = isFixed && offsetParent === win && win.visualViewport ? win.visualViewport.width : offsetParent[widthProp];
1297
1329
  x -= offsetX - popperRect.width;
1298
1330
  x *= gpuAcceleration ? 1 : -1;
1299
1331
  }
@@ -1436,7 +1468,7 @@ function getWindowScrollBarX(element) {
1436
1468
  return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft;
1437
1469
  }
1438
1470
 
1439
- function getViewportRect(element) {
1471
+ function getViewportRect(element, strategy) {
1440
1472
  var win = getWindow(element);
1441
1473
  var html = getDocumentElement(element);
1442
1474
  var visualViewport = win.visualViewport;
@@ -1447,7 +1479,8 @@ function getViewportRect(element) {
1447
1479
  if (visualViewport) {
1448
1480
  width = visualViewport.width;
1449
1481
  height = visualViewport.height;
1450
- if (!/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
1482
+ var layoutViewport = isLayoutViewport();
1483
+ if (layoutViewport || !layoutViewport && strategy === "fixed") {
1451
1484
  x = visualViewport.offsetLeft;
1452
1485
  y = visualViewport.offsetTop;
1453
1486
  }
@@ -1517,8 +1550,8 @@ function rectToClientRect(rect) {
1517
1550
  });
1518
1551
  }
1519
1552
 
1520
- function getInnerBoundingClientRect(element) {
1521
- var rect = getBoundingClientRect(element);
1553
+ function getInnerBoundingClientRect(element, strategy) {
1554
+ var rect = getBoundingClientRect(element, false, strategy === "fixed");
1522
1555
  rect.top = rect.top + element.clientTop;
1523
1556
  rect.left = rect.left + element.clientLeft;
1524
1557
  rect.bottom = rect.top + element.clientHeight;
@@ -1530,8 +1563,8 @@ function getInnerBoundingClientRect(element) {
1530
1563
  return rect;
1531
1564
  }
1532
1565
 
1533
- function getClientRectFromMixedType(element, clippingParent) {
1534
- return clippingParent === viewport ? rectToClientRect(getViewportRect(element)) : isElement(clippingParent) ? getInnerBoundingClientRect(clippingParent) : rectToClientRect(getDocumentRect(getDocumentElement(element)));
1566
+ function getClientRectFromMixedType(element, clippingParent, strategy) {
1567
+ return clippingParent === viewport ? rectToClientRect(getViewportRect(element, strategy)) : isElement(clippingParent) ? getInnerBoundingClientRect(clippingParent, strategy) : rectToClientRect(getDocumentRect(getDocumentElement(element)));
1535
1568
  }
1536
1569
 
1537
1570
  function getClippingParents(element) {
@@ -1546,18 +1579,18 @@ function getClippingParents(element) {
1546
1579
  }));
1547
1580
  }
1548
1581
 
1549
- function getClippingRect(element, boundary, rootBoundary) {
1582
+ function getClippingRect(element, boundary, rootBoundary, strategy) {
1550
1583
  var mainClippingParents = boundary === "clippingParents" ? getClippingParents(element) : [].concat(boundary);
1551
1584
  var clippingParents = [].concat(mainClippingParents, [ rootBoundary ]);
1552
1585
  var firstClippingParent = clippingParents[0];
1553
1586
  var clippingRect = clippingParents.reduce((function(accRect, clippingParent) {
1554
- var rect = getClientRectFromMixedType(element, clippingParent);
1587
+ var rect = getClientRectFromMixedType(element, clippingParent, strategy);
1555
1588
  accRect.top = max(rect.top, accRect.top);
1556
1589
  accRect.right = min(rect.right, accRect.right);
1557
1590
  accRect.bottom = min(rect.bottom, accRect.bottom);
1558
1591
  accRect.left = max(rect.left, accRect.left);
1559
1592
  return accRect;
1560
- }), getClientRectFromMixedType(element, firstClippingParent));
1593
+ }), getClientRectFromMixedType(element, firstClippingParent, strategy));
1561
1594
  clippingRect.width = clippingRect.right - clippingRect.left;
1562
1595
  clippingRect.height = clippingRect.bottom - clippingRect.top;
1563
1596
  clippingRect.x = clippingRect.left;
@@ -1627,12 +1660,12 @@ function detectOverflow(state, options) {
1627
1660
  if (options === void 0) {
1628
1661
  options = {};
1629
1662
  }
1630
- var _options = options, _options$placement = _options.placement, placement = _options$placement === void 0 ? state.placement : _options$placement, _options$boundary = _options.boundary, boundary = _options$boundary === void 0 ? clippingParents : _options$boundary, _options$rootBoundary = _options.rootBoundary, rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary, _options$elementConte = _options.elementContext, elementContext = _options$elementConte === void 0 ? popper : _options$elementConte, _options$altBoundary = _options.altBoundary, altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary, _options$padding = _options.padding, padding = _options$padding === void 0 ? 0 : _options$padding;
1663
+ var _options = options, _options$placement = _options.placement, placement = _options$placement === void 0 ? state.placement : _options$placement, _options$strategy = _options.strategy, strategy = _options$strategy === void 0 ? state.strategy : _options$strategy, _options$boundary = _options.boundary, boundary = _options$boundary === void 0 ? clippingParents : _options$boundary, _options$rootBoundary = _options.rootBoundary, rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary, _options$elementConte = _options.elementContext, elementContext = _options$elementConte === void 0 ? popper : _options$elementConte, _options$altBoundary = _options.altBoundary, altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary, _options$padding = _options.padding, padding = _options$padding === void 0 ? 0 : _options$padding;
1631
1664
  var paddingObject = mergePaddingObject(typeof padding !== "number" ? padding : expandToHashMap(padding, basePlacements));
1632
1665
  var altContext = elementContext === popper ? reference : popper;
1633
1666
  var popperRect = state.rects.popper;
1634
1667
  var element = state.elements[altBoundary ? altContext : elementContext];
1635
- var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary);
1668
+ var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary, strategy);
1636
1669
  var referenceClientRect = getBoundingClientRect(state.elements.reference);
1637
1670
  var popperOffsets = computeOffsets({
1638
1671
  reference: referenceClientRect,
@@ -2035,7 +2068,7 @@ function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) {
2035
2068
  var isOffsetParentAnElement = isHTMLElement(offsetParent);
2036
2069
  var offsetParentIsScaled = isHTMLElement(offsetParent) && isElementScaled(offsetParent);
2037
2070
  var documentElement = getDocumentElement(offsetParent);
2038
- var rect = getBoundingClientRect(elementOrVirtualElement, offsetParentIsScaled);
2071
+ var rect = getBoundingClientRect(elementOrVirtualElement, offsetParentIsScaled, isFixed);
2039
2072
  var scroll = {
2040
2073
  scrollLeft: 0,
2041
2074
  scrollTop: 0
@@ -2270,14 +2303,15 @@ var createPopper = popperGenerator({
2270
2303
  });
2271
2304
 
2272
2305
  class Popover extends Controller {
2273
- static targets=[ "activator", "popover" ];
2306
+ static targets=[ "activator", "popover", "template" ];
2274
2307
  static classes=[ "open", "closed" ];
2275
2308
  static values={
2309
+ appendToBody: Boolean,
2276
2310
  placement: String,
2277
2311
  active: Boolean
2278
2312
  };
2279
2313
  connect() {
2280
- this.popper = createPopper(this.activatorTarget, this.popoverTarget, {
2314
+ const popperOptions = {
2281
2315
  placement: this.placementValue,
2282
2316
  modifiers: [ {
2283
2317
  name: "offset",
@@ -2290,28 +2324,47 @@ class Popover extends Controller {
2290
2324
  allowedAutoPlacements: [ "top-start", "bottom-start", "top-end", "bottom-end" ]
2291
2325
  }
2292
2326
  } ]
2293
- });
2327
+ };
2328
+ if (this.appendToBodyValue) {
2329
+ const clonedTemplate = this.templateTarget.content.cloneNode(true);
2330
+ this.target = clonedTemplate.firstElementChild;
2331
+ popperOptions["strategy"] = "fixed";
2332
+ document.body.appendChild(clonedTemplate);
2333
+ }
2334
+ this.popper = createPopper(this.activatorTarget, this.target, popperOptions);
2294
2335
  if (this.activeValue) {
2295
2336
  this.show();
2296
2337
  }
2297
2338
  }
2298
- toggle() {
2299
- this.popoverTarget.classList.toggle(this.closedClass);
2300
- this.popoverTarget.classList.toggle(this.openClass);
2339
+ async toggle() {
2340
+ this.target.classList.toggle(this.closedClass);
2341
+ this.target.classList.toggle(this.openClass);
2342
+ await this.popper.update();
2301
2343
  }
2302
2344
  async show() {
2303
- this.popoverTarget.classList.remove(this.closedClass);
2304
- this.popoverTarget.classList.add(this.openClass);
2345
+ this.target.classList.remove(this.closedClass);
2346
+ this.target.classList.add(this.openClass);
2305
2347
  await this.popper.update();
2306
2348
  }
2307
2349
  hide(event) {
2308
- if (!this.element.contains(event.target) && !this.popoverTarget.classList.contains(this.closedClass)) {
2309
- this.forceHide();
2310
- }
2350
+ if (this.element.contains(event.target)) return;
2351
+ if (this.target.classList.contains(this.closedClass)) return;
2352
+ if (this.appendToBodyValue && this.target.contains(event.target)) return;
2353
+ this.forceHide();
2311
2354
  }
2312
2355
  forceHide() {
2313
- this.popoverTarget.classList.remove(this.openClass);
2314
- this.popoverTarget.classList.add(this.closedClass);
2356
+ this.target.classList.remove(this.openClass);
2357
+ this.target.classList.add(this.closedClass);
2358
+ }
2359
+ get target() {
2360
+ if (this.hasPopoverTarget) {
2361
+ return this.popoverTarget;
2362
+ } else {
2363
+ return this._target;
2364
+ }
2365
+ }
2366
+ set target(value) {
2367
+ this._target = value;
2315
2368
  }
2316
2369
  }
2317
2370