bard-attachment_field 0.6.3 → 0.6.4
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/Appraisals +1 -0
- data/app/assets/javascripts/input-attachment.js +13 -2
- data/gemfiles/rails_8.1.gemfile +1 -0
- data/input-attachment/src/components/input-attachment/input-attachment.spec.tsx +41 -0
- data/input-attachment/src/components/input-attachment/input-attachment.tsx +24 -1
- 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: 97544f73074b88b48df66fd5a7aa865a8b88fd4eb8078bfc11ff605973c312ed
|
|
4
|
+
data.tar.gz: f29d06de7233f15966ae9b139d10bf0c048fc02aa3c233af72aadcffdd4cf2b6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 118ac9484ef7b4f96a7e2a837fcc38bf3443ca576555ac9d0dcfdab5d16c57074d7927f1e3f454f51c4e2bd83d0fadc60d5810773817825044c7aa5c311dd570
|
|
7
|
+
data.tar.gz: 708a641e9b386a0e91a80f14046711518ca6a9e19a461f894aadefa949c2c36927479a0774fc882a0fea36dbd678f47fb1d787168618e0da76c5a6d943ecdef2
|
data/Appraisals
CHANGED
|
@@ -5179,7 +5179,7 @@ var c3 = proxyCustomElement(class extends H {
|
|
|
5179
5179
|
updateFormValue() {
|
|
5180
5180
|
if (!this.name || !this.internals?.setFormValue) return;
|
|
5181
5181
|
const t2 = new FormData(), e3 = this.files.map(((t3) => t3.value)).filter(((t3) => t3 && "string" != typeof t3 ? (console.error("[input-attachment] Non-string value detected on attachment-file:", typeof t3, t3, Error().stack), false) : "string" == typeof t3 && t3.length > 0));
|
|
5182
|
-
if (this.multiple ? (e3.forEach(((e4) => t2.append(this.name, e4))), 0 === e3.length && t2.append(this.name, "")) : t2.set(this.name, e3[0] || ""), this.internals.setFormValue(t2), this.required && 0 === this.files.length) this.internals.setValidity({ valueMissing: true }, "Please select a file.", this.fileInput);
|
|
5182
|
+
if (this.multiple ? (e3.forEach(((e4) => t2.append(this.name, e4))), 0 === e3.length && t2.append(this.name, "")) : t2.set(this.name, e3[0] || ""), this.internals.setFormValue(t2, this.value), this.required && 0 === this.files.length) this.internals.setValidity({ valueMissing: true }, "Please select a file.", this.fileInput);
|
|
5183
5183
|
else {
|
|
5184
5184
|
const t3 = this.files.map(((t4) => t4.validationError)).filter(((t4) => t4 && t4.length > 0));
|
|
5185
5185
|
t3.length > 0 ? this.internals.setValidity({ customError: true }, t3[0], this.fileInput) : this.internals.setValidity({});
|
|
@@ -5188,6 +5188,17 @@ var c3 = proxyCustomElement(class extends H {
|
|
|
5188
5188
|
reset() {
|
|
5189
5189
|
this.files = [];
|
|
5190
5190
|
}
|
|
5191
|
+
formStateRestoreCallback(t2) {
|
|
5192
|
+
this.restorable(t2) ? this.value = t2 : this.updateFormValue();
|
|
5193
|
+
}
|
|
5194
|
+
restorable(t2) {
|
|
5195
|
+
if ("string" != typeof t2) return false;
|
|
5196
|
+
try {
|
|
5197
|
+
return Array.isArray(JSON.parse(t2));
|
|
5198
|
+
} catch {
|
|
5199
|
+
return false;
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5191
5202
|
handleFileInputChange = () => {
|
|
5192
5203
|
this.fileInput?.files?.length && (this.addFiles(this.fileInput.files), this.fileInput.value = null);
|
|
5193
5204
|
};
|
|
@@ -5215,7 +5226,7 @@ var c3 = proxyCustomElement(class extends H {
|
|
|
5215
5226
|
return this.disabled || !!this.el.closest("fieldset[disabled]");
|
|
5216
5227
|
}
|
|
5217
5228
|
render() {
|
|
5218
|
-
return h(Host, { key: "
|
|
5229
|
+
return h(Host, { key: "9b1ce57edcd31be2885c5923e65c8f4d0ac31002" }, h("input", { key: "a498cdd103d4b3385bfbfb4c2158ae2c15ed3b55", 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: "9edc0c1e22d0a6385515f6dff44965c9f3e7a40c", onClick: () => this.fileInput?.click(), onDrop: this.handleDrop }, h("p", { key: "ae06d2f6be38f25eb977e66b1c6c565ca0e3c97d", part: "title" }, h("strong", { key: "c53fc410ec761ca7f044c25cceb1be12c6e37b5c" }, "Choose ", this.multiple ? "files" : "file", " "), h("span", { key: "227a48d7ed21443ec97f646b58611cbf611145bc" }, "or drag ", this.multiple ? "them" : "it", " here.")), h("div", { key: "25fce75fb15b66bd13bea450054d22ddabd05824", class: "media-preview " + (this.multiple ? "-stacked" : "") }, h("slot", { key: "86f6db9bef3f2d6c16a72679ec33b751aef4eec5" }))));
|
|
5219
5230
|
}
|
|
5220
5231
|
componentDidRender() {
|
|
5221
5232
|
if (0 === this.files.length) {
|
data/gemfiles/rails_8.1.gemfile
CHANGED
|
@@ -34,4 +34,45 @@ describe('input-attachment', () => {
|
|
|
34
34
|
</input-attachment>
|
|
35
35
|
`);
|
|
36
36
|
});
|
|
37
|
+
|
|
38
|
+
describe('formStateRestoreCallback (Firefox session restore)', () => {
|
|
39
|
+
async function newPage() {
|
|
40
|
+
const page = await newSpecPage({
|
|
41
|
+
components: [InputAttachment],
|
|
42
|
+
html: `<form><input-attachment name="attachments[]" multiple></input-attachment></form>`,
|
|
43
|
+
});
|
|
44
|
+
const instance: any = page.rootInstance;
|
|
45
|
+
const calls: any[] = [];
|
|
46
|
+
instance.internals = {
|
|
47
|
+
setFormValue: (value: any, state: any) => calls.push({ value, state }),
|
|
48
|
+
setValidity: () => {},
|
|
49
|
+
};
|
|
50
|
+
return { page, instance, calls };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
it('saves the files as a JSON string state, not the FormData Firefox would flatten', async () => {
|
|
54
|
+
const { instance, calls } = await newPage();
|
|
55
|
+
instance.files = [{ value: 'signed-one' }, { value: 'signed-two' }];
|
|
56
|
+
instance.updateFormValue();
|
|
57
|
+
const last = calls[calls.length - 1];
|
|
58
|
+
expect(typeof last.state).toBe('string');
|
|
59
|
+
expect(JSON.parse(last.state).map((f: any) => f.value)).toEqual(['signed-one', 'signed-two']);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('rebuilds the attachments from the restored state string', async () => {
|
|
63
|
+
const { instance, calls } = await newPage();
|
|
64
|
+
const state = JSON.stringify([{ value: 'signed-one', filename: 'a.png', state: 'complete' }]);
|
|
65
|
+
instance.formStateRestoreCallback(state);
|
|
66
|
+
expect(instance.files.map((f: any) => f.value)).toEqual(['signed-one']);
|
|
67
|
+
instance.updateFormValue();
|
|
68
|
+
expect(calls[calls.length - 1].value.getAll('attachments[]')).toEqual(['signed-one']);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('falls back to the current files when the restored state is the coerced "[object Object]"', async () => {
|
|
72
|
+
const { instance, calls } = await newPage();
|
|
73
|
+
instance.files = [{ value: 'signed-one' }];
|
|
74
|
+
instance.formStateRestoreCallback('[object Object]');
|
|
75
|
+
expect(calls[calls.length - 1].value.getAll('attachments[]')).toEqual(['signed-one']);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
37
78
|
});
|
|
@@ -125,7 +125,9 @@ export class InputAttachment {
|
|
|
125
125
|
// For has_one_attached: set single value (or empty string if none)
|
|
126
126
|
formData.set(this.name, values[0] || '')
|
|
127
127
|
}
|
|
128
|
-
this.
|
|
128
|
+
// State is a JSON string (this.value), not the FormData: Firefox flattens a FormData to {}
|
|
129
|
+
// in the session store and restores it as "[object Object]", but a string survives intact.
|
|
130
|
+
this.internals.setFormValue(formData, this.value)
|
|
129
131
|
|
|
130
132
|
// Update validity state - check for required and child validation errors
|
|
131
133
|
if (this.required && this.files.length === 0) {
|
|
@@ -147,6 +149,27 @@ export class InputAttachment {
|
|
|
147
149
|
this.files = []
|
|
148
150
|
}
|
|
149
151
|
|
|
152
|
+
// On session restore Firefox hands back the state we saved (this.value, a JSON string of the
|
|
153
|
+
// files). Rebuild the attachments from it so they survive a browser restart; fall back to
|
|
154
|
+
// re-deriving from the current files if the state is missing or malformed (e.g. the coerced
|
|
155
|
+
// "[object Object]", which is not valid JSON).
|
|
156
|
+
formStateRestoreCallback(state) {
|
|
157
|
+
if (this.restorable(state)) {
|
|
158
|
+
this.value = state
|
|
159
|
+
} else {
|
|
160
|
+
this.updateFormValue()
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
restorable(state): boolean {
|
|
165
|
+
if (typeof state !== "string") return false
|
|
166
|
+
try {
|
|
167
|
+
return Array.isArray(JSON.parse(state))
|
|
168
|
+
} catch {
|
|
169
|
+
return false
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
150
173
|
handleFileInputChange = () => {
|
|
151
174
|
if (!this.fileInput?.files?.length) return
|
|
152
175
|
this.addFiles(this.fileInput.files)
|
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.6.
|
|
4
|
+
version: 0.6.4
|
|
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-
|
|
11
|
+
date: 2026-09-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activestorage
|