formstrap 0.3.0 → 0.3.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8243dd5f8e2fdae1537ffab42b56734ebdfe53e054feb92c68a323954c0a63e0
4
- data.tar.gz: 0c5a4a73c1d4c518c2ee71cf36f3ccc882ee106a825d94aef212a639ee0bcb94
3
+ metadata.gz: 98d133e0c15d72418662dbe8a98e296a3c701bc97aa9bec3620fa3f0a346ffec
4
+ data.tar.gz: f50c6dc41f0809c54e6522e14a718b0a6842a419fc3068928a64e156df771408
5
5
  SHA512:
6
- metadata.gz: 6dbd8118d294ff8a863675299ce236cc3bd7f7a749b958b42cd22ac66465c3a5c751433edb93009a87b203d28995e2581b3f3600160d4f6a68e0b78110ab9fbe
7
- data.tar.gz: 90ea13586df35f2db36e0b5f3139c6316ae5be21cf022355c665084a19e5d69663efe329f948bd6287b8deb8c4af76ac4b186a94b73aae5b73c8f6aded3659f9
6
+ metadata.gz: f540f4053664dc2095dd165f5e4d6c2c5a7257a7fe810bed486834b3477d5c0888ca4ee9bfc5f0e9ac8685d86e0f97039e5bde5795013a1e80350246580a35d3
7
+ data.tar.gz: 7318bdcee8bb2ac4a84a079374fb15b8385e1032186e8ce14cbe00b449b7d33e2f012b887ad5fa35773eebf9ad718a58c5cacb43188300dc59f27486804882b0
@@ -114,11 +114,11 @@ export default class extends Controller {
114
114
  }
115
115
 
116
116
  minActiveItems () {
117
- return parseInt(this.element.dataset.min, 10) || 0
117
+ return parseInt(this.validationInputTarget.dataset.min, 10) || 0
118
118
  }
119
119
 
120
120
  maxActiveItems () {
121
- return parseInt(this.element.dataset.max, 10) || Infinity
121
+ return parseInt(this.validationInputTarget.dataset.max, 10) || Infinity
122
122
  }
123
123
 
124
124
  resetPositions () {
@@ -131,13 +131,7 @@ export default class extends Controller {
131
131
  }
132
132
 
133
133
  addNewItems (items) {
134
- const itemTargetIds = this.itemTargets.map((i) => { return parseInt(i.querySelectorAll('input')[1].value) })
135
134
  items.forEach((item) => {
136
- if (itemTargetIds.includes(item.blobId)) {
137
- // Do not add this item (as it is already present)
138
- return
139
- }
140
-
141
135
  this.addItem(item)
142
136
  })
143
137
  }
@@ -211,7 +205,7 @@ export default class extends Controller {
211
205
  const returnedBlobIds = elements.map((e) => { return e.blobId })
212
206
 
213
207
  items.forEach((item) => {
214
- const blobId = parseInt(item.querySelectorAll('input')[1].value)
208
+ const blobId = parseInt(item.querySelectorAll('input')[1].value, 10)
215
209
  if (returnedBlobIds.includes(blobId)) {
216
210
  // Do not delete this one
217
211
  return
@@ -237,7 +231,7 @@ export default class extends Controller {
237
231
 
238
232
  itemByBlobId (blobId) {
239
233
  return this.itemTargets.find((item) => {
240
- return item.querySelector('input[name*=\'blob_id\']').value === blobId
234
+ return parseInt(item.querySelector('input[name*=\'blob_id\']').value, 10) === blobId
241
235
  })
242
236
  }
243
237
 
@@ -249,7 +243,7 @@ export default class extends Controller {
249
243
 
250
244
  activeIds () {
251
245
  return this.activeItems().map((item) => {
252
- return item.querySelector('input[name$=\'[blob_id]\']').value
246
+ return parseInt(item.querySelector('input[name$=\'[blob_id]\']').value, 10)
253
247
  })
254
248
  }
255
249
  }
@@ -1,3 +1,4 @@
1
+ /* global XMLHttpRequest, MutationObserver */
1
2
  import { Controller } from '@hotwired/stimulus'
2
3
 
3
4
  export default class extends Controller {
@@ -18,6 +19,7 @@ export default class extends Controller {
18
19
  this.iframeTarget.addEventListener('load', () => {
19
20
  this.hideLoader()
20
21
  this.resizeIframe()
22
+ this.autoResizeIframe()
21
23
  })
22
24
 
23
25
  // Offcanvas closes
@@ -28,6 +30,21 @@ export default class extends Controller {
28
30
  })
29
31
  }
30
32
 
33
+ autoResizeIframe () {
34
+ const observer = new MutationObserver((mutations) => {
35
+ mutations.forEach((mutation) => {
36
+ this.resizeIframe()
37
+ })
38
+ })
39
+
40
+ // Target the body of the iframe for observing
41
+ const innerDoc = this.iframeTarget.contentWindow.document
42
+ observer.observe(innerDoc.body, {
43
+ childList: true, // Listen for additions/removals of child nodes
44
+ subtree: true // Listen for changes in the whole subtree
45
+ })
46
+ }
47
+
31
48
  showLoader () {
32
49
  this.loaderTarget.classList.remove('d-none')
33
50
  }
@@ -60,7 +77,6 @@ export default class extends Controller {
60
77
 
61
78
  requestPreview () {
62
79
  // Create an AJAX request
63
- // eslint-disable-next-line no-undef
64
80
  const xhr = new XMLHttpRequest()
65
81
  xhr.open('POST', this.urlValue, true)
66
82
 
@@ -92,7 +108,7 @@ export default class extends Controller {
92
108
  validateFields () {
93
109
  let allValid = true
94
110
  const fields = this.fieldsTarget
95
- const formElements = fields.querySelectorAll('input[name], select[name], textarea[name]')
111
+ const formElements = fields.querySelectorAll('input:not([type="hidden"]), select[name], textarea[name]')
96
112
  formElements.forEach(function (element) {
97
113
  const isValid = element.reportValidity()
98
114
  if (!isValid) {
@@ -141,7 +157,7 @@ export default class extends Controller {
141
157
  scaleFactor () {
142
158
  const width = this.iframeWrapperTarget.getBoundingClientRect().width
143
159
  const viewportWidth = window.innerWidth
144
- return (width / viewportWidth).toFixed(1)
160
+ return parseFloat((width / viewportWidth).toFixed(1))
145
161
  }
146
162
 
147
163
  // Replace the body of the iframe with the new content
@@ -153,11 +169,21 @@ export default class extends Controller {
153
169
  // Dynamically resize the iFrame to fit its content
154
170
  resizeIframe () {
155
171
  const scaleFactor = this.scaleFactor()
156
- const iframeContentHeight = this.iframeTarget.contentWindow.document.body.scrollHeight
172
+ const iframeContentHeight = this.iFrameContentHeight()
157
173
  const iframeHeight = iframeContentHeight * scaleFactor
158
- this.iframeTarget.style.height = iframeContentHeight + 'px'
174
+
175
+ this.iframeTarget.style.height = `${iframeContentHeight.toFixed()}px`
159
176
  this.iframeTarget.style.opacity = 1
160
- this.iframeWrapperTarget.style.height = iframeHeight + 'px'
177
+ this.iframeWrapperTarget.style.height = `${iframeHeight.toFixed()}px`
178
+ }
179
+
180
+ iFrameContentHeight () {
181
+ const firstElement = this.iframeTarget.contentWindow.document.body.firstElementChild
182
+ const firstElementStyle = window.getComputedStyle(firstElement)
183
+
184
+ const height = firstElement.scrollHeight
185
+ const margins = parseInt(firstElementStyle.marginTop) + parseInt(firstElementStyle.marginBottom)
186
+ return height + margins
161
187
  }
162
188
 
163
189
  getAuthenticityToken () {
@@ -110,6 +110,7 @@ export default class extends Controller {
110
110
  replaceIdsWithTimestamps (template) {
111
111
  const pattern = 'rrrrrrrrr'
112
112
  const replacement = new Date().getTime().toString()
113
+ const regex = new RegExp(pattern, 'g')
113
114
 
114
115
  // Replace ids
115
116
  template.querySelectorAll(`input[id*="${pattern}"], select[id*="${pattern}"], textarea[id*="${pattern}"], button[id*="${pattern}"]`).forEach((node) => {
@@ -117,6 +118,11 @@ export default class extends Controller {
117
118
  node.setAttribute('id', idValue.replace(pattern, replacement))
118
119
  })
119
120
 
121
+ // Search and replace pattern in templates
122
+ template.querySelectorAll('template').forEach((node) => {
123
+ node.innerHTML = node.innerHTML.replace(regex, replacement)
124
+ })
125
+
120
126
  // Replace labels
121
127
  template.querySelectorAll(`label[for*="${pattern}"]`).forEach((node) => {
122
128
  const forValue = node.getAttribute('for')
@@ -11035,10 +11035,10 @@ var media_controller_default = class extends Controller {
11035
11035
  }
11036
11036
  }
11037
11037
  minActiveItems() {
11038
- return parseInt(this.element.dataset.min, 10) || 0;
11038
+ return parseInt(this.validationInputTarget.dataset.min, 10) || 0;
11039
11039
  }
11040
11040
  maxActiveItems() {
11041
- return parseInt(this.element.dataset.max, 10) || Infinity;
11041
+ return parseInt(this.validationInputTarget.dataset.max, 10) || Infinity;
11042
11042
  }
11043
11043
  resetPositions() {
11044
11044
  this.activeItems().forEach((item, index2) => {
@@ -11049,13 +11049,7 @@ var media_controller_default = class extends Controller {
11049
11049
  });
11050
11050
  }
11051
11051
  addNewItems(items) {
11052
- const itemTargetIds = this.itemTargets.map((i) => {
11053
- return parseInt(i.querySelectorAll("input")[1].value);
11054
- });
11055
11052
  items.forEach((item) => {
11056
- if (itemTargetIds.includes(item.blobId)) {
11057
- return;
11058
- }
11059
11053
  this.addItem(item);
11060
11054
  });
11061
11055
  }
@@ -11112,7 +11106,7 @@ var media_controller_default = class extends Controller {
11112
11106
  return e.blobId;
11113
11107
  });
11114
11108
  items.forEach((item) => {
11115
- const blobId = parseInt(item.querySelectorAll("input")[1].value);
11109
+ const blobId = parseInt(item.querySelectorAll("input")[1].value, 10);
11116
11110
  if (returnedBlobIds.includes(blobId)) {
11117
11111
  return;
11118
11112
  }
@@ -11128,7 +11122,7 @@ var media_controller_default = class extends Controller {
11128
11122
  }
11129
11123
  itemByBlobId(blobId) {
11130
11124
  return this.itemTargets.find((item) => {
11131
- return item.querySelector("input[name*='blob_id']").value === blobId;
11125
+ return parseInt(item.querySelector("input[name*='blob_id']").value, 10) === blobId;
11132
11126
  });
11133
11127
  }
11134
11128
  activeItems() {
@@ -11138,7 +11132,7 @@ var media_controller_default = class extends Controller {
11138
11132
  }
11139
11133
  activeIds() {
11140
11134
  return this.activeItems().map((item) => {
11141
- return item.querySelector("input[name$='[blob_id]']").value;
11135
+ return parseInt(item.querySelector("input[name$='[blob_id]']").value, 10);
11142
11136
  });
11143
11137
  }
11144
11138
  };
@@ -11308,6 +11302,7 @@ var nested_preview_controller_default = class extends Controller {
11308
11302
  this.iframeTarget.addEventListener("load", () => {
11309
11303
  this.hideLoader();
11310
11304
  this.resizeIframe();
11305
+ this.autoResizeIframe();
11311
11306
  });
11312
11307
  this.offcanvasTarget.addEventListener("hide.bs.offcanvas", (event) => {
11313
11308
  if (!this.update()) {
@@ -11315,6 +11310,18 @@ var nested_preview_controller_default = class extends Controller {
11315
11310
  }
11316
11311
  });
11317
11312
  }
11313
+ autoResizeIframe() {
11314
+ const observer = new MutationObserver((mutations) => {
11315
+ mutations.forEach((mutation) => {
11316
+ this.resizeIframe();
11317
+ });
11318
+ });
11319
+ const innerDoc = this.iframeTarget.contentWindow.document;
11320
+ observer.observe(innerDoc.body, {
11321
+ childList: true,
11322
+ subtree: true
11323
+ });
11324
+ }
11318
11325
  showLoader() {
11319
11326
  this.loaderTarget.classList.remove("d-none");
11320
11327
  }
@@ -11361,7 +11368,7 @@ var nested_preview_controller_default = class extends Controller {
11361
11368
  validateFields() {
11362
11369
  let allValid = true;
11363
11370
  const fields = this.fieldsTarget;
11364
- const formElements = fields.querySelectorAll("input[name], select[name], textarea[name]");
11371
+ const formElements = fields.querySelectorAll('input:not([type="hidden"]), select[name], textarea[name]');
11365
11372
  formElements.forEach(function(element) {
11366
11373
  const isValid = element.reportValidity();
11367
11374
  if (!isValid) {
@@ -11396,7 +11403,7 @@ var nested_preview_controller_default = class extends Controller {
11396
11403
  scaleFactor() {
11397
11404
  const width = this.iframeWrapperTarget.getBoundingClientRect().width;
11398
11405
  const viewportWidth = window.innerWidth;
11399
- return (width / viewportWidth).toFixed(1);
11406
+ return parseFloat((width / viewportWidth).toFixed(1));
11400
11407
  }
11401
11408
  updatePreview(html) {
11402
11409
  this.iframeTarget.contentWindow.document.body.innerHTML = html;
@@ -11404,11 +11411,18 @@ var nested_preview_controller_default = class extends Controller {
11404
11411
  }
11405
11412
  resizeIframe() {
11406
11413
  const scaleFactor = this.scaleFactor();
11407
- const iframeContentHeight = this.iframeTarget.contentWindow.document.body.scrollHeight;
11414
+ const iframeContentHeight = this.iFrameContentHeight();
11408
11415
  const iframeHeight = iframeContentHeight * scaleFactor;
11409
- this.iframeTarget.style.height = iframeContentHeight + "px";
11416
+ this.iframeTarget.style.height = `${iframeContentHeight.toFixed()}px`;
11410
11417
  this.iframeTarget.style.opacity = 1;
11411
- this.iframeWrapperTarget.style.height = iframeHeight + "px";
11418
+ this.iframeWrapperTarget.style.height = `${iframeHeight.toFixed()}px`;
11419
+ }
11420
+ iFrameContentHeight() {
11421
+ const firstElement = this.iframeTarget.contentWindow.document.body.firstElementChild;
11422
+ const firstElementStyle = window.getComputedStyle(firstElement);
11423
+ const height = firstElement.scrollHeight;
11424
+ const margins = parseInt(firstElementStyle.marginTop) + parseInt(firstElementStyle.marginBottom);
11425
+ return height + margins;
11412
11426
  }
11413
11427
  getAuthenticityToken() {
11414
11428
  const tokenTag = document.querySelector('meta[name="csrf-token"]');
@@ -13199,10 +13213,14 @@ var repeater_controller_default = class extends Controller {
13199
13213
  replaceIdsWithTimestamps(template) {
13200
13214
  const pattern = "rrrrrrrrr";
13201
13215
  const replacement = new Date().getTime().toString();
13216
+ const regex = new RegExp(pattern, "g");
13202
13217
  template.querySelectorAll(`input[id*="${pattern}"], select[id*="${pattern}"], textarea[id*="${pattern}"], button[id*="${pattern}"]`).forEach((node) => {
13203
13218
  const idValue = node.getAttribute("id");
13204
13219
  node.setAttribute("id", idValue.replace(pattern, replacement));
13205
13220
  });
13221
+ template.querySelectorAll("template").forEach((node) => {
13222
+ node.innerHTML = node.innerHTML.replace(regex, replacement);
13223
+ });
13206
13224
  template.querySelectorAll(`label[for*="${pattern}"]`).forEach((node) => {
13207
13225
  const forValue = node.getAttribute("for");
13208
13226
  node.setAttribute("for", forValue.replace(pattern, replacement));
@@ -1,14 +1,14 @@
1
1
  class ApplicationHelper
2
2
  def show_svg(attachment, options = {})
3
- return if !attachment.content_type.include?('svg')
3
+ return if !attachment.content_type.include?("svg")
4
4
 
5
5
  attachment.open do |file|
6
6
  content = file.read
7
7
  doc = Nokogiri::HTML::DocumentFragment.parse content
8
- svg = doc.at_css 'svg'
8
+ svg = doc.at_css "svg"
9
9
 
10
10
  # for security
11
- doc.search('script').each do |src|
11
+ doc.search("script").each do |src|
12
12
  src.remove
13
13
  end
14
14
 
@@ -17,4 +17,4 @@ class ApplicationHelper
17
17
  doc.to_html.html_safe
18
18
  end
19
19
  end
20
- end
20
+ end
@@ -19,8 +19,6 @@ module Formstrap
19
19
  data: {
20
20
  controller: "media",
21
21
  name: name,
22
- min: min,
23
- max: max,
24
22
  sort: sort,
25
23
  accept: accept,
26
24
  required: required.nil? ? 0 : required
@@ -31,7 +29,7 @@ module Formstrap
31
29
  def item_options
32
30
  options = {
33
31
  sort: sort,
34
- url: modal_url,
32
+ url: modal_url
35
33
  }
36
34
 
37
35
  # Don't pass width or height if it was not defined
@@ -108,10 +106,10 @@ module Formstrap
108
106
  end
109
107
 
110
108
  def min
111
- if @required
112
- (@min.to_i < 1) ? 1 : @min.to_i
109
+ if @min.to_i < 1
110
+ @required ? 1 : 0
113
111
  else
114
- (@min.to_i < 1) ? 0 : @min.to_i
112
+ @min.to_i
115
113
  end
116
114
  end
117
115
 
@@ -124,7 +122,14 @@ module Formstrap
124
122
  end
125
123
 
126
124
  def modal_url
127
- formstrap_media_path(name: name, ids: blob_ids, min: min, max: max, mimetype: accept, exclude_models: exclude_models)
125
+ formstrap_media_path(
126
+ name: name,
127
+ ids: blob_ids,
128
+ min: min,
129
+ max: max,
130
+ mimetype: accept,
131
+ exclude_models: exclude_models
132
+ )
128
133
  end
129
134
 
130
135
  def edit_modal_url(attachment)
@@ -92,10 +92,10 @@ module Formstrap
92
92
  blob.open do |file|
93
93
  content = file.read
94
94
  doc = Nokogiri::HTML::DocumentFragment.parse content
95
- svg = doc.at_css 'svg'
95
+ svg = doc.at_css "svg"
96
96
 
97
97
  # for security
98
- doc.search('script').each do |src|
98
+ doc.search("script").each do |src|
99
99
  src.remove
100
100
  end
101
101
 
@@ -5,6 +5,8 @@
5
5
  class: "formstrap-media-validation",
6
6
  data: {
7
7
  "media-target": "validationInput",
8
+ "min": min,
9
+ "max": max,
8
10
  "min-message": t(".min", count: min),
9
11
  "max-message": t(".max", count: max),
10
12
  } %>
@@ -2,7 +2,7 @@
2
2
  <!-- Preview placeholder -->
3
3
  <div class="nested-preview-iframe-wrapper position-relative" role="button" data-nested-preview-target="iframeWrapper" data-bs-toggle="offcanvas" data-bs-target="#offcanvas-<%= form.options[:child_index] %>" aria-controls="offcanvasRight" data-turbo-cache="false">
4
4
  <iframe src="<%= url %>" class="pe-none" data-nested-preview-target="iframe"></iframe>
5
- <div data-nested-preview-target="loader" class="nested-preview-loader terti">
5
+ <div data-nested-preview-target="loader" class="nested-preview-loader">
6
6
  <div class="spinner-grow text-secondary" role="status">
7
7
  <span class="visually-hidden">Loading...</span>
8
8
  </div>
@@ -18,7 +18,7 @@
18
18
  <div class="offcanvas-body">
19
19
 
20
20
  <div class="alert alert-danger d-none" data-nested-preview-target="error">
21
- <% t(".error") %>
21
+ <%= t(".error") %>
22
22
  </div>
23
23
 
24
24
  <!-- Row content -->
@@ -15,7 +15,7 @@ module Formstrap
15
15
  default_options = {
16
16
  data: {
17
17
  controller: "preview",
18
- "preview-url-value": options[:url]
18
+ "preview-url-value": options[:url]
19
19
  },
20
20
  type: nil
21
21
  }
@@ -1,3 +1,3 @@
1
1
  module Formstrap
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.2"
3
3
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontierdotbe/formstrap",
3
- "version": "0.2.0",
3
+ "version": "0.3.2",
4
4
  "description": "Bootstrap-powered Form Helpers",
5
5
  "module": "app/assets/javascripts/formstrap.js",
6
6
  "main": "app/assets/javascripts/formstrap.js",
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: formstrap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jef Vlamings
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-03-11 00:00:00.000000000 Z
11
+ date: 2024-03-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: An extensive Bootstrap form library to power your Ruby On Rails application.
14
14
  email: