bard-attachment_field 0.5.8 → 0.6.1
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 +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +20 -0
- data/app/assets/javascripts/input-attachment.js +27 -18
- data/input-attachment/package.json +1 -1
- data/input-attachment/src/components/attachment-file/attachment-file.spec.tsx +22 -0
- data/input-attachment/src/components/attachment-file/attachment-file.tsx +3 -1
- data/input-attachment/src/components/attachment-file/readme.md +2 -0
- data/input-attachment/src/components/input-attachment/input-attachment.tsx +4 -0
- data/input-attachment/src/components.d.ts +6 -0
- data/lib/bard/attachment_field/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6bef7d86b4bd8938fe4944c96dde471a868af65c72817f254bad2ef989840eb3
|
|
4
|
+
data.tar.gz: b40d5ad52b14aa85e4a695dbc7f2aaa58cf2ae6d75b11860100a7818805a1e51
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8907713c89cfab1718f7a812a23c49b604abe65e4a613669760f63d8a22790ffaed0ac19af0682262237a9557d3c45221f7c5f4ede50255cbec48564e412418f
|
|
7
|
+
data.tar.gz: cf825d567f49628992b93247ac2668b6adf35204dda72c0aa1701fe537bf247d9214bfb14d688e64d2e4593e70b4720dc449d3a345c1d4451ccaf62b48243395
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.1] - 2026-06-24
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
- Bump `@botandrose/progress-bar` to 0.6.1, which moves the `progressbar` role
|
|
8
|
+
off the host onto the inner fill/ring and names it from its slotted content.
|
|
9
|
+
This resolves the `aria-progressbar-name` and `nested-interactive` axe
|
|
10
|
+
violations on `<attachment-file>`, whose download link is slotted as the
|
|
11
|
+
progress bar's content.
|
|
12
|
+
|
|
13
|
+
## [0.6.0] - 2026-06-11
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
- `<attachment-file>` accepts optional `href` and `download` attributes for its
|
|
18
|
+
download link, falling back to `src` and `filename`. Lets block-rendered
|
|
19
|
+
fields point the preview at a small image while the download link fetches
|
|
20
|
+
the original file.
|
|
21
|
+
|
|
3
22
|
## [0.5.5] - 2026-06-06
|
|
4
23
|
|
|
5
24
|
### Bug Fixes
|
data/README.md
CHANGED
|
@@ -73,6 +73,26 @@ Pass a block to render your own children inside the `<input-attachment>` element
|
|
|
73
73
|
<% end %>
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
`src` powers both the preview and the download link. When `src` points at a
|
|
77
|
+
smaller preview image, set `href` to point the download link at the original
|
|
78
|
+
file instead, and optionally `download` to control the downloaded filename:
|
|
79
|
+
|
|
80
|
+
```erb
|
|
81
|
+
<%= form.attachment_field :image do |options| %>
|
|
82
|
+
<% if @post.image.attached? %>
|
|
83
|
+
<attachment-file
|
|
84
|
+
name="<%= options["name"] %>"
|
|
85
|
+
src="<%= url_for @post.image.variant(:thumb) %>"
|
|
86
|
+
href="<%= rails_blob_path @post.image, disposition: :attachment %>"
|
|
87
|
+
download="<%= @post.image.filename %>"
|
|
88
|
+
filename="<%= @post.image.filename %>"
|
|
89
|
+
value="<%= @post.image.signed_id %>"
|
|
90
|
+
preview="true"
|
|
91
|
+
filetype="image"></attachment-file>
|
|
92
|
+
<% end %>
|
|
93
|
+
<% end %>
|
|
94
|
+
```
|
|
95
|
+
|
|
76
96
|
## Development
|
|
77
97
|
|
|
78
98
|
This project has two parts: the Ruby gem and the Stencil web components in `input-attachment/`.
|
|
@@ -4332,11 +4332,11 @@ var b = class {
|
|
|
4332
4332
|
l.create(this.file, ((e3, i2) => {
|
|
4333
4333
|
if (e3) return void t2(e3);
|
|
4334
4334
|
const s = new f(this.file, i2, this.url, this.customHeaders);
|
|
4335
|
-
|
|
4335
|
+
w(this.delegate, "directUploadWillCreateBlobWithXHR", s.xhr), s.create(((e4) => {
|
|
4336
4336
|
if (e4) t2(e4);
|
|
4337
4337
|
else {
|
|
4338
4338
|
const e5 = new m(s);
|
|
4339
|
-
|
|
4339
|
+
w(this.delegate, "directUploadWillStoreFileWithXHR", e5.xhr), e5.create(((e6) => {
|
|
4340
4340
|
e6 ? t2(e6) : t2(null, s.toJSON());
|
|
4341
4341
|
}));
|
|
4342
4342
|
}
|
|
@@ -4344,10 +4344,10 @@ var b = class {
|
|
|
4344
4344
|
}));
|
|
4345
4345
|
}
|
|
4346
4346
|
};
|
|
4347
|
-
function
|
|
4347
|
+
function w(t2, e3, ...i2) {
|
|
4348
4348
|
if (t2 && "function" == typeof t2[e3]) return t2[e3](...i2);
|
|
4349
4349
|
}
|
|
4350
|
-
var
|
|
4350
|
+
var v = class {
|
|
4351
4351
|
constructor(t2, e3) {
|
|
4352
4352
|
this.input = t2, this.file = e3, this.directUpload = new b(this.file, this.url, this), this.dispatch("initialize");
|
|
4353
4353
|
}
|
|
@@ -4411,7 +4411,7 @@ var y = class {
|
|
|
4411
4411
|
const t2 = [];
|
|
4412
4412
|
return this.inputs.forEach(((e3) => {
|
|
4413
4413
|
p(e3.files).forEach(((i2) => {
|
|
4414
|
-
const s = new
|
|
4414
|
+
const s = new v(e3, i2);
|
|
4415
4415
|
t2.push(s);
|
|
4416
4416
|
}));
|
|
4417
4417
|
})), t2;
|
|
@@ -4714,6 +4714,8 @@ var H2 = proxyCustomElement(class extends H {
|
|
|
4714
4714
|
value = "";
|
|
4715
4715
|
filename;
|
|
4716
4716
|
src;
|
|
4717
|
+
href;
|
|
4718
|
+
download;
|
|
4717
4719
|
filetype;
|
|
4718
4720
|
size;
|
|
4719
4721
|
state = "complete";
|
|
@@ -4766,7 +4768,7 @@ var H2 = proxyCustomElement(class extends H {
|
|
|
4766
4768
|
"error" !== this.state && (this.state = "complete", this.percent = 100);
|
|
4767
4769
|
}
|
|
4768
4770
|
render() {
|
|
4769
|
-
return h(Host, { key: "
|
|
4771
|
+
return h(Host, { key: "8468877f30120b32201867ab5466d4d0fed5e514" }, h("slot", { key: "f44af091a15ff9ae425a61b708c5ea64a19a5057" }), h("figure", { key: "c629a6dece58606c37979d8d21cfe37d2312511e" }, h("div", { key: "87b9629c3133c7050cf84ec6a8824a68dadb81d0", class: "progress-details" }, h("progress-bar", { key: "63238eeda3adb36ce17a355d4cb2db0dbf45f0df", percent: this.percent, class: this.state, error: "error" === this.state }, h("a", { key: "dacdd7679ee07177fc5a18dcca018dcb6b32406e", class: "download-link", href: this.href || this.src, download: this.download || this.filename, onClick: (t2) => t2.stopPropagation() }, this.filename)), h("span", { key: "5618922137596fc76b569ac876064a0444d8d73a", class: "progress-icon" }), h("a", { key: "6ac34bc373c61db029a1e0101ea2219846e960fd", class: "remove-media", onClick: this.removeClicked, href: "#" }, h("span", { key: "ef9f0d6e8ba6fab4d876cd788e75a34ada1fe572" }, "Remove media")), this.uploadError && this._file ? h("a", { class: "retry-media", onClick: this.retryClicked, href: "#" }, h("span", null, "Retry upload")) : ""), this.validationError || this.uploadError ? h("p", { class: "validation-error" }, this.validationError || this.uploadError) : "", this.preview ? h("attachment-preview", { src: this.src, filetype: this.filetype }) : ""));
|
|
4770
4772
|
}
|
|
4771
4773
|
componentDidLoad() {
|
|
4772
4774
|
"pending" == this.state && this._file && (this.checkValidity() ? (this.controller = new C(this.el, this._file), this.controller.dispatch("initialize", { controller: this.controller })) : this.state = "error");
|
|
@@ -4781,7 +4783,7 @@ var H2 = proxyCustomElement(class extends H {
|
|
|
4781
4783
|
static get style() {
|
|
4782
4784
|
return `:host{display:block;width:100%;max-width:100%;font-size:13px}figure{margin:0}.progress-details{position:relative;display:flex;align-items:center}progress-bar{flex:1 0}progress-bar.pending{opacity:0.5}progress-bar.complete{opacity:0.8}progress-bar:not(.complete)+.progress-icon{display:none}progress-bar.complete+.progress-icon{content:url('data:image/svg+xml;utf8,<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve"><g><path d="M6.3,9.1c0.2,0,0.5,0.1,0.7,0.4c0.5,0.5,1,1,1.4,1.4c0.3,0.3,0.3,0.3,0.6,0c1.4-1.3,2.7-2.6,4-3.9c0.3-0.3,0.6-0.4,1-0.4 c0.5,0.1,0.9,0.6,0.7,1.1c-0.1,0.2-0.2,0.4-0.3,0.6c-1.6,1.6-3.2,3.2-4.8,4.8c-0.5,0.5-1,0.5-1.6,0c-0.8-0.7-1.5-1.5-2.3-2.3 c-0.3-0.3-0.5-0.6-0.3-1.1C5.5,9.3,5.8,9.1,6.3,9.1z"/></g></svg>');filter:invert(100%)}.progress-icon{display:inline-block;flex:0 0 20px;width:28px;height:28px;background-size:contain;position:absolute;right:30px;z-index:1}progress-bar.error{background:var(--input-attachment-error-bg, rgba(74, 70, 70, 0.25));opacity:1}.progress-bar a{color:#fff}.download-link{padding-right:20px;color:#fff}.remove-media{display:inline-block;content:url('data:image/svg+xml;utf8,<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve"><g><path d="M0,19.9C0.2,8.5,9.2-0.1,20.1,0C31.8,0.1,40.2,9.5,40,20.4c-0.2,11-8.9,19.7-20.1,19.6C8,39.9,0,30.5,0,19.9z M20,3.7 c-9,0-16.3,7-16.3,16.2C3.7,29,10.9,36.3,20,36.3c9,0,16.3-7.1,16.4-16.3C36.3,11,29.2,3.8,20,3.7z"/><path d="M17.3,20c-0.2-0.2-0.3-0.4-0.5-0.6c-1-1-2-1.9-2.9-2.9c-0.5-0.5-0.8-1.1-0.7-1.9c0.1-0.7,0.5-1.2,1.2-1.4 c0.8-0.2,1.5,0,2.1,0.6c1,1,2,2,3,3.1c0.3,0.4,0.6,0.3,0.9,0c1-1,2-2,3-3c0.3-0.3,0.7-0.5,1.1-0.6c0.8-0.2,1.6,0.1,2,0.8 c0.4,0.8,0.3,1.7-0.4,2.4c-1,1-2,2-3,3c-0.2,0.2-0.3,0.4-0.5,0.6c1.2,1.2,2.3,2.3,3.4,3.4c0.6,0.6,0.9,1.3,0.6,2.2 c-0.4,1.1-1.7,1.6-2.6,1c-0.3-0.2-0.5-0.4-0.8-0.6c-1-1-1.9-1.9-2.9-2.9c-0.3-0.3-0.5-0.3-0.9,0c-1,1-2,2.1-3,3 c-0.4,0.4-1,0.6-1.5,0.8c-0.6,0.1-1.2-0.2-1.5-0.8c-0.4-0.6-0.5-1.3-0.1-1.9c0.2-0.3,0.4-0.5,0.6-0.7C15.1,22.3,16.2,21.2,17.3,20z "/></g></svg>');flex:0 0 25px;width:25px;height:20px;align-items:center;opacity:0.25}.remove-media:hover{opacity:1;filter:invert(50%)sepia(100%)saturate(10000%)}.remove-media span{display:inline-block;text-indent:-9999px;color:transparent}.retry-media{color:var(--input-attachment-error-color, #c00);font-size:12px;text-decoration:underline;cursor:pointer;padding-left:8px}.retry-media:hover{color:var(--input-attachment-error-color-hover, #900)}.validation-error{color:var(--input-attachment-error-color, #c00);font-size:12px;margin:4px 0 0 10px}`;
|
|
4783
4785
|
}
|
|
4784
|
-
}, [769, "attachment-file", { name: [1537], accepts: [1537], max: [1538], url: [1537], value: [1537], filename: [1537], src: [1537], filetype: [1537], size: [1538], state: [1537], percent: [1538], preview: [1540], validationMessage: [1, "validation-message"] }, [[0, "direct-upload:initialize", "start"], [0, "direct-upload:start", "start"], [0, "direct-upload:progress", "progress"], [0, "direct-upload:error", "error"], [0, "direct-upload:end", "end"]], { filename: [{ setMissingFiletype: 0 }] }]);
|
|
4786
|
+
}, [769, "attachment-file", { name: [1537], accepts: [1537], max: [1538], url: [1537], value: [1537], filename: [1537], src: [1537], href: [1537], download: [1537], filetype: [1537], size: [1538], state: [1537], percent: [1538], preview: [1540], validationMessage: [1, "validation-message"] }, [[0, "direct-upload:initialize", "start"], [0, "direct-upload:start", "start"], [0, "direct-upload:progress", "progress"], [0, "direct-upload:error", "error"], [0, "direct-upload:end", "end"]], { filename: [{ setMissingFiletype: 0 }] }]);
|
|
4785
4787
|
|
|
4786
4788
|
// dist/components/attachment-file.js
|
|
4787
4789
|
var o = H2;
|
|
@@ -4806,11 +4808,14 @@ var t = class extends HTMLElement {
|
|
|
4806
4808
|
super(), this.attachShadow({ mode: "open" }), this._percent = null, this._renderedMode = null, this._rateTimer = null;
|
|
4807
4809
|
}
|
|
4808
4810
|
connectedCallback() {
|
|
4809
|
-
this.render(), this.
|
|
4811
|
+
this.render(), this.updateBar(), this._syncTicker();
|
|
4810
4812
|
}
|
|
4811
4813
|
disconnectedCallback() {
|
|
4812
4814
|
this._stopTicker();
|
|
4813
4815
|
}
|
|
4816
|
+
get _progressbarEl() {
|
|
4817
|
+
return this.shadowRoot.querySelector("circular" === this.mode ? ".ring" : ".bar");
|
|
4818
|
+
}
|
|
4814
4819
|
get percent() {
|
|
4815
4820
|
return this._percent;
|
|
4816
4821
|
}
|
|
@@ -4847,13 +4852,14 @@ var t = class extends HTMLElement {
|
|
|
4847
4852
|
}
|
|
4848
4853
|
updateBar() {
|
|
4849
4854
|
if ("circular" === this.mode) {
|
|
4850
|
-
const
|
|
4851
|
-
|
|
4855
|
+
const r5 = this.shadowRoot?.querySelector(".progress-ring");
|
|
4856
|
+
r5 && (r5.style.strokeDashoffset = this.indeterminate ? "" : String(100 * (1 - this._percent / 100)));
|
|
4852
4857
|
} else {
|
|
4853
|
-
const
|
|
4854
|
-
|
|
4858
|
+
const r5 = this.shadowRoot?.querySelector(".bar");
|
|
4859
|
+
r5 && (r5.style.width = this.indeterminate ? "" : `${this._percent}%`);
|
|
4855
4860
|
}
|
|
4856
|
-
|
|
4861
|
+
const r4 = this._progressbarEl;
|
|
4862
|
+
r4 && (this.indeterminate ? r4.removeAttribute("aria-valuenow") : r4.setAttribute("aria-valuenow", String(this._percent)));
|
|
4857
4863
|
}
|
|
4858
4864
|
_syncTicker() {
|
|
4859
4865
|
const r4 = Number(this.getAttribute("rate")), n3 = this.isConnected && !this.indeterminate && Number.isFinite(r4) && 0 !== r4;
|
|
@@ -4868,7 +4874,8 @@ var t = class extends HTMLElement {
|
|
|
4868
4874
|
}
|
|
4869
4875
|
render() {
|
|
4870
4876
|
const n3 = this.mode;
|
|
4871
|
-
|
|
4877
|
+
if (this._renderedMode === n3) return;
|
|
4878
|
+
this._renderedMode = n3, this.shadowRoot.adoptedStyleSheets = [(r || (r = new CSSStyleSheet(), r.replaceSync(`
|
|
4872
4879
|
:host {
|
|
4873
4880
|
--progress-color: #2E7D32;
|
|
4874
4881
|
--error-color: #7a242f;
|
|
@@ -5010,7 +5017,9 @@ var t = class extends HTMLElement {
|
|
|
5010
5017
|
:host([error]) .progress-ring { display: none; }
|
|
5011
5018
|
:host([error]) .ring { animation: none; }
|
|
5012
5019
|
:host([error]) .error-mark { display: block; }
|
|
5013
|
-
`)), r)], this.shadowRoot.innerHTML = "circular" === n3 ? '\n <div class="circular">\n <svg class="ring" viewBox="0 0 100 100">\n <circle class="track" cx="50" cy="50" r="40"></circle>\n <circle class="progress-ring" cx="50" cy="50" r="40" pathLength="100"></circle>\n <path class="error-mark" d="M37 37 L63 63 M63 37 L37 63"></path>\n </svg>\n <span class="label"><slot></slot></span>\n </div>\n' : '\n <div class="bar"></div>\n <div class="text"><slot></slot></div>\n'
|
|
5020
|
+
`)), r)], this.shadowRoot.innerHTML = "circular" === n3 ? '\n <div class="circular">\n <svg class="ring" viewBox="0 0 100 100">\n <circle class="track" cx="50" cy="50" r="40"></circle>\n <circle class="progress-ring" cx="50" cy="50" r="40" pathLength="100"></circle>\n <path class="error-mark" d="M37 37 L63 63 M63 37 L37 63"></path>\n </svg>\n <span class="label" id="label"><slot></slot></span>\n </div>\n' : '\n <div class="bar"></div>\n <div class="text" id="label"><slot></slot></div>\n';
|
|
5021
|
+
const e3 = this._progressbarEl;
|
|
5022
|
+
e3.setAttribute("role", "progressbar"), e3.setAttribute("aria-valuemin", "0"), e3.setAttribute("aria-valuemax", "100"), e3.setAttribute("aria-labelledby", "label");
|
|
5014
5023
|
}
|
|
5015
5024
|
};
|
|
5016
5025
|
customElements.get("progress-bar") || customElements.define("progress-bar", t);
|
|
@@ -5158,13 +5167,13 @@ var c3 = proxyCustomElement(class extends H {
|
|
|
5158
5167
|
this._files = t2, this.multiple || (this._files = this._files.slice(-1)), forceUpdate(this.el), this.fireChangeEvent();
|
|
5159
5168
|
}
|
|
5160
5169
|
get value() {
|
|
5161
|
-
return JSON.stringify(this.files.map(((t2) => ({ value: t2.value, filename: t2.filename, src: t2.src, state: t2.state, percent: t2.percent, size: t2.size, filetype: t2.filetype }))));
|
|
5170
|
+
return JSON.stringify(this.files.map(((t2) => ({ value: t2.value, filename: t2.filename, src: t2.src, href: t2.href, download: t2.download, state: t2.state, percent: t2.percent, size: t2.size, filetype: t2.filetype }))));
|
|
5162
5171
|
}
|
|
5163
5172
|
set value(t2) {
|
|
5164
5173
|
const e3 = JSON.parse(t2 || "[]");
|
|
5165
5174
|
0 !== e3.length ? (this.files = e3.map(((t3) => {
|
|
5166
5175
|
const e4 = document.createElement("attachment-file");
|
|
5167
|
-
return e4.name = this.name, e4.preview = this.preview, e4.value = t3.value, e4.filename = t3.filename, e4.src = t3.src, e4.state = t3.state || "complete", e4.percent = t3.percent || 100, e4.size = t3.size, e4.filetype = t3.filetype, e4;
|
|
5176
|
+
return e4.name = this.name, e4.preview = this.preview, e4.value = t3.value, e4.filename = t3.filename, e4.src = t3.src, e4.href = t3.href, e4.download = t3.download, e4.state = t3.state || "complete", e4.percent = t3.percent || 100, e4.size = t3.size, e4.filetype = t3.filetype, e4;
|
|
5168
5177
|
})), requestAnimationFrame((() => this.componentDidRender()))) : this.files = [];
|
|
5169
5178
|
}
|
|
5170
5179
|
updateFormValue() {
|
|
@@ -5206,7 +5215,7 @@ var c3 = proxyCustomElement(class extends H {
|
|
|
5206
5215
|
return this.disabled || !!this.el.closest("fieldset[disabled]");
|
|
5207
5216
|
}
|
|
5208
5217
|
render() {
|
|
5209
|
-
return h(Host, { key: "
|
|
5218
|
+
return h(Host, { key: "09588ce0af3411cc579d7d828c604fcc1fd44825" }, h("input", { key: "1311e26122c5778efc9eb1176c4a4986f1865d7d", ref: (t2) => this.fileInput = t2, type: "file", "aria-label": "Choose " + (this.multiple ? "files" : "file"), multiple: this.multiple, accept: this.accepts, required: this.required && 0 === this.files.length, disabled: this.isDisabled, onChange: () => this.handleFileInputChange() }), h("file-drop", { key: "7adac8601feb97f1123482bde83cf0bb7bd931b7", onClick: () => this.fileInput?.click(), onDrop: this.handleDrop }, h("p", { key: "38faec2ec357d905719948b3f0f46b23abde4c40", part: "title" }, h("strong", { key: "61bd69a635852191f31e4e986f13bbf63e57dfa3" }, "Choose ", this.multiple ? "files" : "file", " "), h("span", { key: "6ac8e031e03ed7d23c83edbacde03c441c4b4df7" }, "or drag ", this.multiple ? "them" : "it", " here.")), h("div", { key: "476bdeafcf7e6211a3feccdc0c275efaf1bff928", class: "media-preview " + (this.multiple ? "-stacked" : "") }, h("slot", { key: "f843426c3a3cef23ccf8d3f1f584075762ecc8b2" }))));
|
|
5210
5219
|
}
|
|
5211
5220
|
componentDidRender() {
|
|
5212
5221
|
if (0 === this.files.length) {
|
|
@@ -17,4 +17,26 @@ describe('attachment-file', () => {
|
|
|
17
17
|
// Just check that it renders without error
|
|
18
18
|
expect(page.root.tagName).toBe('ATTACHMENT-FILE');
|
|
19
19
|
});
|
|
20
|
+
|
|
21
|
+
it('links the download link to src by default', async () => {
|
|
22
|
+
const page = await newSpecPage({
|
|
23
|
+
components: [AttachmentFile],
|
|
24
|
+
html: `<attachment-file src="/preview.jpg" filename="image.jpg"></attachment-file>`,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const link = page.root.shadowRoot.querySelector('a.download-link');
|
|
28
|
+
expect(link.getAttribute('href')).toBe('/preview.jpg');
|
|
29
|
+
expect(link.getAttribute('download')).toBe('image.jpg');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('links the download link to href and download when provided', async () => {
|
|
33
|
+
const page = await newSpecPage({
|
|
34
|
+
components: [AttachmentFile],
|
|
35
|
+
html: `<attachment-file src="/preview.jpg" href="/master.jpg" download="original.jpg" filename="image.jpg"></attachment-file>`,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const link = page.root.shadowRoot.querySelector('a.download-link');
|
|
39
|
+
expect(link.getAttribute('href')).toBe('/master.jpg');
|
|
40
|
+
expect(link.getAttribute('download')).toBe('original.jpg');
|
|
41
|
+
});
|
|
20
42
|
});
|
|
@@ -22,6 +22,8 @@ export class AttachmentFile {
|
|
|
22
22
|
@Prop({ reflect: true, mutable: true }) value: string = ""
|
|
23
23
|
@Prop({ reflect: true, mutable: true }) filename: string
|
|
24
24
|
@Prop({ reflect: true, mutable: true }) src: string
|
|
25
|
+
@Prop({ reflect: true, mutable: true }) href: string
|
|
26
|
+
@Prop({ reflect: true, mutable: true }) download: string
|
|
25
27
|
@Prop({ reflect: true, mutable: true }) filetype: string
|
|
26
28
|
@Prop({ reflect: true, mutable: true }) size: number
|
|
27
29
|
@Prop({ reflect: true, mutable: true }) state: string = "complete"
|
|
@@ -132,7 +134,7 @@ export class AttachmentFile {
|
|
|
132
134
|
<figure>
|
|
133
135
|
<div class="progress-details">
|
|
134
136
|
<progress-bar percent={this.percent} class={this.state} error={this.state === "error"}>
|
|
135
|
-
<a class="download-link" href={this.src} download={this.filename} onClick={e => e.stopPropagation()}>
|
|
137
|
+
<a class="download-link" href={this.href || this.src} download={this.download || this.filename} onClick={e => e.stopPropagation()}>
|
|
136
138
|
{this.filename}
|
|
137
139
|
</a>
|
|
138
140
|
</progress-bar>
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
| Property | Attribute | Description | Type | Default |
|
|
11
11
|
| ------------------- | -------------------- | ----------- | --------- | ------------ |
|
|
12
12
|
| `accepts` | `accepts` | | `string` | `undefined` |
|
|
13
|
+
| `download` | `download` | | `string` | `undefined` |
|
|
13
14
|
| `filename` | `filename` | | `string` | `undefined` |
|
|
14
15
|
| `filetype` | `filetype` | | `string` | `undefined` |
|
|
16
|
+
| `href` | `href` | | `string` | `undefined` |
|
|
15
17
|
| `max` | `max` | | `number` | `undefined` |
|
|
16
18
|
| `name` | `name` | | `string` | `undefined` |
|
|
17
19
|
| `percent` | `percent` | | `number` | `100` |
|
|
@@ -73,6 +73,8 @@ export class InputAttachment {
|
|
|
73
73
|
value: f.value,
|
|
74
74
|
filename: f.filename,
|
|
75
75
|
src: f.src,
|
|
76
|
+
href: f.href,
|
|
77
|
+
download: f.download,
|
|
76
78
|
state: f.state,
|
|
77
79
|
percent: f.percent,
|
|
78
80
|
size: f.size,
|
|
@@ -93,6 +95,8 @@ export class InputAttachment {
|
|
|
93
95
|
attachmentFile.value = item.value
|
|
94
96
|
attachmentFile.filename = item.filename
|
|
95
97
|
attachmentFile.src = item.src
|
|
98
|
+
attachmentFile.href = item.href
|
|
99
|
+
attachmentFile.download = item.download
|
|
96
100
|
attachmentFile.state = item.state || 'complete'
|
|
97
101
|
attachmentFile.percent = item.percent || 100
|
|
98
102
|
attachmentFile.size = item.size
|
|
@@ -8,8 +8,10 @@ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
|
|
|
8
8
|
export namespace Components {
|
|
9
9
|
interface AttachmentFile {
|
|
10
10
|
"accepts": string;
|
|
11
|
+
"download": string;
|
|
11
12
|
"filename": string;
|
|
12
13
|
"filetype": string;
|
|
14
|
+
"href": string;
|
|
13
15
|
"max": number;
|
|
14
16
|
"name": string;
|
|
15
17
|
/**
|
|
@@ -126,8 +128,10 @@ declare global {
|
|
|
126
128
|
declare namespace LocalJSX {
|
|
127
129
|
interface AttachmentFile {
|
|
128
130
|
"accepts"?: string;
|
|
131
|
+
"download"?: string;
|
|
129
132
|
"filename"?: string;
|
|
130
133
|
"filetype"?: string;
|
|
134
|
+
"href"?: string;
|
|
131
135
|
"max"?: number;
|
|
132
136
|
"name"?: string;
|
|
133
137
|
"onAttachment-file:ready"?: (event: AttachmentFileCustomEvent<any>) => void;
|
|
@@ -199,6 +203,8 @@ declare namespace LocalJSX {
|
|
|
199
203
|
"value": string;
|
|
200
204
|
"filename": string;
|
|
201
205
|
"src": string;
|
|
206
|
+
"href": string;
|
|
207
|
+
"download": string;
|
|
202
208
|
"filetype": string;
|
|
203
209
|
"size": number;
|
|
204
210
|
"state": string;
|
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.
|
|
4
|
+
version: 0.6.1
|
|
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-06-
|
|
11
|
+
date: 2026-06-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activestorage
|