bard-attachment_field 0.2.4 → 0.2.5

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: f060d98d9b6c3c4e0494845d868b7f702009df53e1cd39ac0d659402a8c28b97
4
- data.tar.gz: a2e2afa88506420373c09e818612a037eb111a910370c87a2f41c69968d84151
3
+ metadata.gz: e7ee47362d9328e10ce601c919f111a0e9ea5fe99545b699dd4bebac3afaeb62
4
+ data.tar.gz: '0803e538c1fdbb231dee995f69d0bcb071c765e6dfcd52d5e38f237a353bacd1'
5
5
  SHA512:
6
- metadata.gz: 1480f10a1aaf61dc8f14e7c0e97062b033e85aa8d97d47e6df857cf1d254ad3b3adbee192d3de2156dc19dc3d2de16f244c9b6561933483c70a797ba63a0eecc
7
- data.tar.gz: c78324d6d01030eeb10180287c14c6218e122ad865b511c89c7613ce8515b04aa3f138d4cdf8e0aab35216b66443478fafb6e6314955cfe9b01d06b1320493fb
6
+ metadata.gz: 7f285040a99291e3224ba2c3c8f8259e4a67ff8c6901f15356517752363706271d4f12765680151f523070ebd967429c4940f43d99a04b9d9b09fbf9e7d7be70
7
+ data.tar.gz: 6f0e372c7eb699efb282e71de7ff8156f7f7615d5cd82bba896697f961a78beeb5b72cf24adaf47ce58598d306879c2ce6c6cf1ca1cf619df16c14299bf2aacf
@@ -5070,9 +5070,6 @@ var FetchResponse = class {
5070
5070
  get isTurboStream() {
5071
5071
  return this.contentType.match(/^text\/vnd\.turbo-stream\.html/);
5072
5072
  }
5073
- get isScript() {
5074
- return this.contentType.match(/\b(?:java|ecma)script\b/);
5075
- }
5076
5073
  async renderTurboStream() {
5077
5074
  if (this.isTurboStream) {
5078
5075
  if (window.Turbo) {
@@ -5084,22 +5081,6 @@ var FetchResponse = class {
5084
5081
  return Promise.reject(new Error(`Expected a Turbo Stream response but got "${this.contentType}" instead`));
5085
5082
  }
5086
5083
  }
5087
- async activeScript() {
5088
- if (this.isScript) {
5089
- const script = document.createElement("script");
5090
- const metaTag = document.querySelector("meta[name=csp-nonce]");
5091
- if (metaTag) {
5092
- const nonce = metaTag.nonce === "" ? metaTag.content : metaTag.nonce;
5093
- if (nonce) {
5094
- script.setAttribute("nonce", nonce);
5095
- }
5096
- }
5097
- script.innerHTML = await this.text;
5098
- document.body.appendChild(script);
5099
- } else {
5100
- return Promise.reject(new Error(`Expected a Script response but got "${this.contentType}" instead`));
5101
- }
5102
- }
5103
5084
  };
5104
5085
  var RequestInterceptor = class {
5105
5086
  static register(interceptor) {
@@ -5145,7 +5126,7 @@ function stringEntriesFromFormData(formData) {
5145
5126
  function mergeEntries(searchParams, entries) {
5146
5127
  for (const [name, value] of entries) {
5147
5128
  if (value instanceof window.File) continue;
5148
- if (searchParams.has(name) && !name.includes("[]")) {
5129
+ if (searchParams.has(name)) {
5149
5130
  searchParams.delete(name);
5150
5131
  searchParams.set(name, value);
5151
5132
  } else {
@@ -5168,16 +5149,11 @@ var FetchRequest = class {
5168
5149
  } catch (error) {
5169
5150
  console.error(error);
5170
5151
  }
5171
- const fetch = window.Turbo ? window.Turbo.fetch : window.fetch;
5172
- const response = new FetchResponse(await fetch(this.url, this.fetchOptions));
5152
+ const response = new FetchResponse(await window.fetch(this.url, this.fetchOptions));
5173
5153
  if (response.unauthenticated && response.authenticationURL) {
5174
5154
  return Promise.reject(window.location.href = response.authenticationURL);
5175
5155
  }
5176
- if (response.isScript) {
5177
- await response.activeScript();
5178
- }
5179
- const responseStatusIsTurboStreamable = response.ok || response.unprocessableEntity;
5180
- if (responseStatusIsTurboStreamable && response.isTurboStream) {
5156
+ if (response.ok && response.isTurboStream) {
5181
5157
  await response.renderTurboStream();
5182
5158
  }
5183
5159
  return response;
@@ -5187,38 +5163,27 @@ var FetchRequest = class {
5187
5163
  headers[key] = value;
5188
5164
  this.options.headers = headers;
5189
5165
  }
5190
- sameHostname() {
5191
- if (!this.originalUrl.startsWith("http:") && !this.originalUrl.startsWith("https:")) {
5192
- return true;
5193
- }
5194
- try {
5195
- return new URL(this.originalUrl).hostname === window.location.hostname;
5196
- } catch (_) {
5197
- return true;
5198
- }
5199
- }
5200
5166
  get fetchOptions() {
5201
5167
  return {
5202
5168
  method: this.method.toUpperCase(),
5203
5169
  headers: this.headers,
5204
5170
  body: this.formattedBody,
5205
5171
  signal: this.signal,
5206
- credentials: this.credentials,
5207
- redirect: this.redirect,
5208
- keepalive: this.keepalive
5172
+ credentials: "same-origin",
5173
+ redirect: this.redirect
5209
5174
  };
5210
5175
  }
5211
5176
  get headers() {
5212
- const baseHeaders = {
5213
- "X-Requested-With": "XMLHttpRequest",
5214
- "Content-Type": this.contentType,
5215
- Accept: this.accept
5216
- };
5217
- if (this.sameHostname()) {
5218
- baseHeaders["X-CSRF-Token"] = this.csrfToken;
5219
- }
5220
5177
  return compact(
5221
- Object.assign(baseHeaders, this.additionalHeaders)
5178
+ Object.assign(
5179
+ {
5180
+ "X-Requested-With": "XMLHttpRequest",
5181
+ "X-CSRF-Token": this.csrfToken,
5182
+ "Content-Type": this.contentType,
5183
+ Accept: this.accept
5184
+ },
5185
+ this.additionalHeaders
5186
+ )
5222
5187
  );
5223
5188
  }
5224
5189
  get csrfToken() {
@@ -5242,8 +5207,6 @@ var FetchRequest = class {
5242
5207
  return "text/vnd.turbo-stream.html, text/html, application/xhtml+xml";
5243
5208
  case "json":
5244
5209
  return "application/json, application/vnd.api+json";
5245
- case "script":
5246
- return "text/javascript, application/javascript";
5247
5210
  default:
5248
5211
  return "*/*";
5249
5212
  }
@@ -5278,12 +5241,6 @@ var FetchRequest = class {
5278
5241
  get redirect() {
5279
5242
  return this.options.redirect || "follow";
5280
5243
  }
5281
- get credentials() {
5282
- return this.options.credentials || "same-origin";
5283
- }
5284
- get keepalive() {
5285
- return this.options.keepalive || false;
5286
- }
5287
5244
  get additionalHeaders() {
5288
5245
  return this.options.headers || {};
5289
5246
  }
@@ -5494,9 +5451,7 @@ var FormController = class _FormController {
5494
5451
  </dialog>`);
5495
5452
  this.dialog = this.element.querySelector("#form-controller-dialog");
5496
5453
  this.progressContainerTarget = this.dialog.querySelector("#progress-container");
5497
- if (this.element.dataset.remote !== "true" && (this.element.dataset.turbo == "false" || !window.Turbo?.session?.enabled)) {
5498
- this.element.addEventListener("submit", (event) => this.submit(event));
5499
- }
5454
+ this.element.addEventListener("submit", (event) => this.submit(event));
5500
5455
  window.addEventListener("beforeunload", (event) => this.beforeUnload(event));
5501
5456
  this.element.addEventListener("direct-upload:initialize", (event) => this.init(event));
5502
5457
  this.element.addEventListener("direct-upload:start", (event) => this.start(event));
@@ -5512,10 +5467,11 @@ var FormController = class _FormController {
5512
5467
  }
5513
5468
  }
5514
5469
  submit(event) {
5515
- if (this.controllers.length === 0 && !this.hasUploadErrors())
5470
+ if (this.controllers.length === 0 && !this.hasUploadErrors() && !this.processing)
5516
5471
  return;
5517
5472
  event.preventDefault();
5518
5473
  this.submitted = true;
5474
+ this.setInputAttachmentsDisabled(true);
5519
5475
  this.startNextController();
5520
5476
  if (this.processing) {
5521
5477
  this.dialog.showModal();
@@ -5527,10 +5483,18 @@ var FormController = class _FormController {
5527
5483
  const controller = this.controllers.shift();
5528
5484
  if (controller) {
5529
5485
  this.processing = true;
5530
- this.setInputAttachmentsDisabled(true);
5486
+ if (this.submitted) {
5487
+ this.setInputAttachmentsDisabled(true);
5488
+ } else {
5489
+ this.setControllerInputDisabled(controller, true);
5490
+ }
5531
5491
  controller.start((error) => {
5532
- if (error) {
5533
- this.setInputAttachmentsDisabled(false);
5492
+ if (this.submitted) {
5493
+ if (error) {
5494
+ this.setInputAttachmentsDisabled(false);
5495
+ }
5496
+ } else {
5497
+ this.setControllerInputDisabled(controller, false);
5534
5498
  }
5535
5499
  this.processing = false;
5536
5500
  this.startNextController();
@@ -5543,16 +5507,22 @@ var FormController = class _FormController {
5543
5507
  return Array.from(this.element.querySelectorAll("attachment-file")).some((el) => el.state === "error");
5544
5508
  }
5545
5509
  submitForm() {
5546
- if (this.submitted) {
5547
- if (this.hasUploadErrors()) {
5548
- this.dialog.close();
5549
- this.setInputAttachmentsDisabled(false);
5550
- return;
5551
- }
5552
- this.setInputAttachmentsDisabled(true);
5553
- window.setTimeout(() => {
5554
- this.element.submit();
5555
- }, 10);
5510
+ if (!this.submitted)
5511
+ return;
5512
+ if (this.hasUploadErrors()) {
5513
+ this.dialog.close();
5514
+ this.setInputAttachmentsDisabled(false);
5515
+ return;
5516
+ }
5517
+ this.setInputAttachmentsDisabled(true);
5518
+ requestAnimationFrame(() => {
5519
+ this.element.submit();
5520
+ });
5521
+ }
5522
+ setControllerInputDisabled(controller, disabled) {
5523
+ const inputAttachment = controller.uploadedFile.closest("input-attachment");
5524
+ if (inputAttachment) {
5525
+ inputAttachment.disabled = disabled;
5556
5526
  }
5557
5527
  }
5558
5528
  setInputAttachmentsDisabled(disabled) {
@@ -21,7 +21,10 @@
21
21
  },
22
22
  "jest": {
23
23
  "testEnvironment": "jsdom",
24
- "extensionsToTreatAsEsm": [".ts", ".tsx"],
24
+ "extensionsToTreatAsEsm": [
25
+ ".ts",
26
+ ".tsx"
27
+ ],
25
28
  "globals": {
26
29
  "ts-jest": {
27
30
  "useESM": true
@@ -53,5 +56,8 @@
53
56
  "import": "./dist/components/index.js"
54
57
  }
55
58
  },
56
- "license": "MIT"
59
+ "license": "MIT",
60
+ "volta": {
61
+ "node": "22.22.1"
62
+ }
57
63
  }
@@ -34,9 +34,7 @@ export default class FormController {
34
34
  this.dialog = this.element.querySelector("#form-controller-dialog")
35
35
  this.progressContainerTarget = this.dialog.querySelector("#progress-container")
36
36
 
37
- if(this.element.dataset.remote !== "true" && (this.element.dataset.turbo == "false" || !(window as any).Turbo?.session?.enabled)) {
38
- this.element.addEventListener("submit", event => this.submit(event))
39
- }
37
+ this.element.addEventListener("submit", event => this.submit(event))
40
38
  window.addEventListener("beforeunload", event => this.beforeUnload(event))
41
39
 
42
40
  this.element.addEventListener("direct-upload:initialize", event => this.init(event))
@@ -56,9 +54,10 @@ export default class FormController {
56
54
  }
57
55
 
58
56
  submit(event) {
59
- if(this.controllers.length === 0 && !this.hasUploadErrors()) return
57
+ if(this.controllers.length === 0 && !this.hasUploadErrors() && !this.processing) return
60
58
  event.preventDefault()
61
59
  this.submitted = true
60
+ this.setInputAttachmentsDisabled(true)
62
61
  this.startNextController()
63
62
  if(this.processing) {
64
63
  this.dialog.showModal()
@@ -71,10 +70,18 @@ export default class FormController {
71
70
  const controller = this.controllers.shift()
72
71
  if(controller) {
73
72
  this.processing = true
74
- this.setInputAttachmentsDisabled(true)
73
+ if (this.submitted) {
74
+ this.setInputAttachmentsDisabled(true)
75
+ } else {
76
+ this.setControllerInputDisabled(controller, true)
77
+ }
75
78
  controller.start(error => {
76
- if(error) {
77
- this.setInputAttachmentsDisabled(false)
79
+ if (this.submitted) {
80
+ if(error) {
81
+ this.setInputAttachmentsDisabled(false)
82
+ }
83
+ } else {
84
+ this.setControllerInputDisabled(controller, false)
78
85
  }
79
86
  this.processing = false
80
87
  this.startNextController()
@@ -90,16 +97,22 @@ export default class FormController {
90
97
  }
91
98
 
92
99
  submitForm() {
93
- if(this.submitted) {
94
- if(this.hasUploadErrors()) {
95
- this.dialog.close()
96
- this.setInputAttachmentsDisabled(false)
97
- return
98
- }
99
- this.setInputAttachmentsDisabled(true)
100
- window.setTimeout(() => { // allow other async tasks to complete
101
- this.element.submit()
102
- }, 10)
100
+ if(!this.submitted) return
101
+ if(this.hasUploadErrors()) {
102
+ this.dialog.close()
103
+ this.setInputAttachmentsDisabled(false)
104
+ return
105
+ }
106
+ this.setInputAttachmentsDisabled(true)
107
+ requestAnimationFrame(() => { // run after pending rAF callbacks (e.g. updateFormValue)
108
+ this.element.submit()
109
+ })
110
+ }
111
+
112
+ setControllerInputDisabled(controller: DirectUploadController, disabled: boolean) {
113
+ const inputAttachment = (controller.uploadedFile as any).closest('input-attachment')
114
+ if (inputAttachment) {
115
+ inputAttachment.disabled = disabled
103
116
  }
104
117
  }
105
118
 
@@ -309,6 +309,22 @@ Then "the {string} attachment field should be disabled" do |field|
309
309
  expect(is_disabled).to be true
310
310
  end
311
311
 
312
+ Then "the {string} attachment field should not be disabled" do |field|
313
+ element = Bard::AttachmentField::TestHelper.find_field(page, field)
314
+
315
+ is_disabled = page.evaluate_script(<<~JS)
316
+ (function() {
317
+ const element = document.getElementById('#{element[:id]}');
318
+ if (element.hasAttribute('disabled')) return true;
319
+ if (element.closest('fieldset[disabled]')) return true;
320
+ const fileInput = element.shadowRoot?.querySelector('input[type="file"]');
321
+ return fileInput?.disabled || false;
322
+ })()
323
+ JS
324
+
325
+ expect(is_disabled).to be false
326
+ end
327
+
312
328
  Then "the {string} attachment field should have a validation error containing {string}" do |field, message|
313
329
  element = Bard::AttachmentField::TestHelper.find_field(page, field)
314
330
  messages = Bard::AttachmentField::TestHelper.validation_messages(page, element)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Bard
4
4
  module AttachmentField
5
- VERSION = "0.2.4"
5
+ VERSION = "0.2.5"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bard-attachment_field
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Micah Geisel
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-03-04 00:00:00.000000000 Z
11
+ date: 2026-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activestorage