bard-attachment_field 0.3.1 → 0.4.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/app/assets/javascripts/input-attachment.js +15 -71
- data/input-attachment/README.md +49 -5
- data/input-attachment/bun.lockb +0 -0
- data/input-attachment/package.json +1 -1
- data/input-attachment/src/components/attachment-file/attachment-file.css +16 -5
- data/input-attachment/src/components/input-attachment/input-attachment.css +23 -9
- data/input-attachment/src/components/input-attachment/input-attachment.tsx +10 -79
- 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: 455080b5539151ab0d19ff817c8a8ca1c81af69abc7de283f61ee6753f94cdc2
|
|
4
|
+
data.tar.gz: 3bc698d1aca4b7cdbe50dab75758bdf65b2a818fa8d3a3ce17651d7ed97a7ced
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a5625056035d5e5aa4e1d51de78a21081f3b5e233db5c1582167537f936d904c5cc70070d55a234b308958cc25da90588c0a1543e779613b70bd091d10dbf8b7
|
|
7
|
+
data.tar.gz: e860eb4c5f28cd7b1c4519cbc06dc3d227c5dcb0b2cf4a3bc6d74aae77d9709dc5f0d418a04c3965a411ec5edb17d1c03df24aef0e491c346a40bd83be05d641
|
|
@@ -5280,7 +5280,7 @@ var request = (verb, url, payload, headers) => {
|
|
|
5280
5280
|
});
|
|
5281
5281
|
};
|
|
5282
5282
|
var get = (url, payload = {}, headers = {}) => request("get", url, payload, headers);
|
|
5283
|
-
var attachmentFileCss = `: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;padding:0 10px}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
|
|
5283
|
+
var attachmentFileCss = `: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;padding:0 10px}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}@media (prefers-color-scheme: dark){:host{--input-attachment-error-color:#f66;--input-attachment-error-color-hover:#f99;--input-attachment-error-bg:rgba(200, 100, 100, 0.2)}.remove-media{filter:invert(1)}}`;
|
|
5284
5284
|
var AttachmentFile = /* @__PURE__ */ proxyCustomElement(class AttachmentFile2 extends H {
|
|
5285
5285
|
constructor(registerHost2) {
|
|
5286
5286
|
super();
|
|
@@ -5715,11 +5715,12 @@ var ProgressBar = class extends HTMLElement {
|
|
|
5715
5715
|
--bar-height: 32px;
|
|
5716
5716
|
--bar-radius: 4px;
|
|
5717
5717
|
--bar-padding: 8px;
|
|
5718
|
+
--bar-border-color: #999;
|
|
5718
5719
|
|
|
5719
5720
|
display: block;
|
|
5720
5721
|
position: relative;
|
|
5721
5722
|
padding: var(--bar-padding);
|
|
5722
|
-
border: 1px solid
|
|
5723
|
+
border: 1px solid var(--bar-border-color);
|
|
5723
5724
|
border-radius: var(--bar-radius);
|
|
5724
5725
|
}
|
|
5725
5726
|
|
|
@@ -5753,7 +5754,7 @@ var ProgressBar = class extends HTMLElement {
|
|
|
5753
5754
|
if (!customElements.get("progress-bar")) {
|
|
5754
5755
|
customElements.define("progress-bar", ProgressBar);
|
|
5755
5756
|
}
|
|
5756
|
-
var inputAttachmentCss = ":host{display:block;padding:25px;color:var(--input-attachment-text-color, #000);font-size:13px}:host *{box-sizing:border-box;position:relative}file-drop{cursor:pointer;display:block;outline-offset:-10px;background:rgba(255,255,255, 0.25);padding:20px;text-align:center;transition:all 0.15s;outline:2px dashed rgba(0,0,0,0.25);color
|
|
5757
|
+
var inputAttachmentCss = ":host{display:block;padding:25px;color:var(--input-attachment-text-color, #000);font-size:13px}:host *{box-sizing:border-box;position:relative}file-drop{cursor:pointer;display:block;outline-offset:-10px;background:var(--input-attachment-drop-bg, rgba(255,255,255, 0.25));padding:20px;text-align:center;transition:all 0.15s;outline:2px dashed var(--input-attachment-drop-border, rgba(0,0,0,0.25));color:var(--input-attachment-drop-color, #444);font-size:14px}file-drop.-full{width:100%}p{padding:10px 20px;margin:0}.-dragover{background:var(--input-attachment-drop-bg-active, rgba(255,255,255,0.5));outline:2px dashed var(--input-attachment-drop-border, rgba(0,0,0,0.25))}.media-preview{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:center}// UPLOADER .direct-upload-wrapper{position:fixed;z-index:9999;top:0;left:0;width:100vw;height:100vh;display:flex;align-items:center;justify-content:center;background:var(--input-attachment-overlay-bg, rgba(#333, 0.9))}.direct-upload-content{display:block;background:var(--input-attachment-dialog-bg, #fcfcfc);color:var(--input-attachment-text-color, #000);padding:40px 60px 60px;border-radius:3px;width:60vw}.direct-upload-content h3{border-bottom:2px solid var(--input-attachment-dialog-border, #1f1f1f);margin-bottom:20px}.separate-upload{padding:0 10px;margin-top:10px;font-size:0.9em}.direct-upload--pending{opacity:0.6}.direct-upload--complete{opacity:0.4}.direct-upload--error{border-color:var(--input-attachment-error-color, red)}input[type=file][data-direct-upload-url][disabled]{display:none}:host.separate-upload{padding:0 10px;margin-top:10px;font-size:0.9em}@media (prefers-color-scheme: dark){:host{--input-attachment-text-color:#e0e0e0;--input-attachment-drop-bg:rgba(255,255,255, 0.08);--input-attachment-drop-border:rgba(255,255,255, 0.25);--input-attachment-drop-color:#bbb;--input-attachment-drop-bg-active:rgba(255,255,255, 0.15);--input-attachment-dialog-bg:#2a2a2a;--input-attachment-dialog-border:#555;--input-attachment-error-color:#f66}}";
|
|
5757
5758
|
var InputAttachment$1 = /* @__PURE__ */ proxyCustomElement(class InputAttachment extends H {
|
|
5758
5759
|
get el() {
|
|
5759
5760
|
return this;
|
|
@@ -5788,54 +5789,12 @@ var InputAttachment$1 = /* @__PURE__ */ proxyCustomElement(class InputAttachment
|
|
|
5788
5789
|
const existingFiles = Array.from(this.el.children).filter((e) => e.tagName == "ATTACHMENT-FILE");
|
|
5789
5790
|
if (existingFiles.length > 0)
|
|
5790
5791
|
this.files = existingFiles;
|
|
5791
|
-
this.restoreFromLocalStorage();
|
|
5792
5792
|
if (this.files.length > 0)
|
|
5793
5793
|
this.updateFormValue();
|
|
5794
5794
|
}
|
|
5795
5795
|
componentDidLoad() {
|
|
5796
|
-
if (this.form) {
|
|
5797
|
-
this.form.addEventListener("submit", () => this.clearLocalStorage());
|
|
5798
|
-
}
|
|
5799
5796
|
this.fileInput?.addEventListener("change", this.handleFileInputChange);
|
|
5800
5797
|
}
|
|
5801
|
-
get localStorageKey() {
|
|
5802
|
-
if (!this.form || !this.name)
|
|
5803
|
-
return null;
|
|
5804
|
-
const formId = this.form.id || this.form.action || "form";
|
|
5805
|
-
return `input-attachment:${formId}:${this.name}`;
|
|
5806
|
-
}
|
|
5807
|
-
saveToLocalStorage() {
|
|
5808
|
-
const key = this.localStorageKey;
|
|
5809
|
-
if (!key)
|
|
5810
|
-
return;
|
|
5811
|
-
const data = this.persistenceData;
|
|
5812
|
-
if (data.length > 0) {
|
|
5813
|
-
localStorage.setItem(key, JSON.stringify(data));
|
|
5814
|
-
} else {
|
|
5815
|
-
localStorage.removeItem(key);
|
|
5816
|
-
}
|
|
5817
|
-
}
|
|
5818
|
-
restoreFromLocalStorage() {
|
|
5819
|
-
const key = this.localStorageKey;
|
|
5820
|
-
if (!key || this.files.length > 0)
|
|
5821
|
-
return;
|
|
5822
|
-
try {
|
|
5823
|
-
const stored = localStorage.getItem(key);
|
|
5824
|
-
if (stored) {
|
|
5825
|
-
const data = JSON.parse(stored);
|
|
5826
|
-
if (Array.isArray(data) && data.length > 0) {
|
|
5827
|
-
this.restoreFromPersistence(data);
|
|
5828
|
-
}
|
|
5829
|
-
}
|
|
5830
|
-
} catch (e) {
|
|
5831
|
-
}
|
|
5832
|
-
}
|
|
5833
|
-
clearLocalStorage() {
|
|
5834
|
-
const key = this.localStorageKey;
|
|
5835
|
-
if (key) {
|
|
5836
|
-
localStorage.removeItem(key);
|
|
5837
|
-
}
|
|
5838
|
-
}
|
|
5839
5798
|
// Methods
|
|
5840
5799
|
get files() {
|
|
5841
5800
|
return this._files;
|
|
@@ -5848,23 +5807,7 @@ var InputAttachment$1 = /* @__PURE__ */ proxyCustomElement(class InputAttachment
|
|
|
5848
5807
|
this.fireChangeEvent();
|
|
5849
5808
|
}
|
|
5850
5809
|
get value() {
|
|
5851
|
-
return this.files.map((
|
|
5852
|
-
}
|
|
5853
|
-
set value(val) {
|
|
5854
|
-
const newValue = val || [];
|
|
5855
|
-
if (JSON.stringify(this.value) !== JSON.stringify(newValue)) {
|
|
5856
|
-
this.files = newValue.map((signedId) => {
|
|
5857
|
-
const attachmentFile = document.createElement("attachment-file");
|
|
5858
|
-
attachmentFile.name = this.name;
|
|
5859
|
-
attachmentFile.preview = this.preview;
|
|
5860
|
-
attachmentFile.signedId = signedId;
|
|
5861
|
-
return attachmentFile;
|
|
5862
|
-
});
|
|
5863
|
-
}
|
|
5864
|
-
}
|
|
5865
|
-
// For form-persistence: store complete attachment data (not just signed_ids)
|
|
5866
|
-
get persistenceData() {
|
|
5867
|
-
return this.files.map((f) => ({
|
|
5810
|
+
return JSON.stringify(this.files.map((f) => ({
|
|
5868
5811
|
value: f.value,
|
|
5869
5812
|
filename: f.filename,
|
|
5870
5813
|
src: f.src,
|
|
@@ -5872,11 +5815,14 @@ var InputAttachment$1 = /* @__PURE__ */ proxyCustomElement(class InputAttachment
|
|
|
5872
5815
|
percent: f.percent,
|
|
5873
5816
|
size: f.size,
|
|
5874
5817
|
filetype: f.filetype
|
|
5875
|
-
}));
|
|
5818
|
+
})));
|
|
5876
5819
|
}
|
|
5877
|
-
|
|
5878
|
-
|
|
5820
|
+
set value(val) {
|
|
5821
|
+
const data = JSON.parse(val || "[]");
|
|
5822
|
+
if (data.length === 0) {
|
|
5823
|
+
this.files = [];
|
|
5879
5824
|
return;
|
|
5825
|
+
}
|
|
5880
5826
|
this.files = data.map((item) => {
|
|
5881
5827
|
const attachmentFile = document.createElement("attachment-file");
|
|
5882
5828
|
attachmentFile.name = this.name;
|
|
@@ -5896,7 +5842,7 @@ var InputAttachment$1 = /* @__PURE__ */ proxyCustomElement(class InputAttachment
|
|
|
5896
5842
|
if (!this.name || !this.internals?.setFormValue)
|
|
5897
5843
|
return;
|
|
5898
5844
|
const formData = new FormData();
|
|
5899
|
-
const values = this.value.filter((v) => v);
|
|
5845
|
+
const values = this.files.map((f) => f.value).filter((v) => v);
|
|
5900
5846
|
if (this.multiple) {
|
|
5901
5847
|
values.forEach((signedId) => formData.append(this.name, signedId));
|
|
5902
5848
|
if (values.length === 0)
|
|
@@ -5905,7 +5851,6 @@ var InputAttachment$1 = /* @__PURE__ */ proxyCustomElement(class InputAttachment
|
|
|
5905
5851
|
formData.set(this.name, values[0] || "");
|
|
5906
5852
|
}
|
|
5907
5853
|
this.internals.setFormValue(formData);
|
|
5908
|
-
this.saveToLocalStorage();
|
|
5909
5854
|
if (this.required && this.files.length === 0) {
|
|
5910
5855
|
this.internals.setValidity({ valueMissing: true }, "Please select a file.", this.fileInput);
|
|
5911
5856
|
} else {
|
|
@@ -5918,8 +5863,7 @@ var InputAttachment$1 = /* @__PURE__ */ proxyCustomElement(class InputAttachment
|
|
|
5918
5863
|
}
|
|
5919
5864
|
}
|
|
5920
5865
|
reset() {
|
|
5921
|
-
this.
|
|
5922
|
-
this.clearLocalStorage();
|
|
5866
|
+
this.files = [];
|
|
5923
5867
|
}
|
|
5924
5868
|
handleFileInputChange = () => {
|
|
5925
5869
|
if (!this.fileInput?.files?.length)
|
|
@@ -5956,12 +5900,12 @@ var InputAttachment$1 = /* @__PURE__ */ proxyCustomElement(class InputAttachment
|
|
|
5956
5900
|
return this.disabled || !!this.el.closest("fieldset[disabled]");
|
|
5957
5901
|
}
|
|
5958
5902
|
render() {
|
|
5959
|
-
return h(Host, { key: "
|
|
5903
|
+
return h(Host, { key: "d6cdfb923931f8232c1a6118dcb38889266cc5d0" }, h("input", { key: "e77f98d380aa090cabc70f712fd0787c0cf1373b", ref: (el) => this.fileInput = el, type: "file", multiple: this.multiple, accept: this.accepts, required: this.required && this.files.length === 0, disabled: this.isDisabled, onChange: () => this.handleFileInputChange(), style: {
|
|
5960
5904
|
opacity: "0.01",
|
|
5961
5905
|
width: "1px",
|
|
5962
5906
|
height: "1px",
|
|
5963
5907
|
zIndex: "-999"
|
|
5964
|
-
} }), h("file-drop", { key: "
|
|
5908
|
+
} }), h("file-drop", { key: "87f1d105a94d63b1ff23061cca0787d6dcf909cf", onClick: () => this.fileInput?.click(), onDrop: this.handleDrop }, h("p", { key: "97fbb415cf9d528f1813a4c4ab512819f63e1838", part: "title" }, h("strong", { key: "f856b14d87bee688d2bec237b0cdef748f9ddc33" }, "Choose ", this.multiple ? "files" : "file", " "), h("span", { key: "c51929dd34c8826a434ee15dc577ce8ae4e65c72" }, "or drag ", this.multiple ? "them" : "it", " here.")), h("div", { key: "9a847df1d30360e515f6dd69652841ff56efb915", class: `media-preview ${this.multiple ? "-stacked" : ""}` }, h("slot", { key: "e2d06462bddde0191f1071eb6f1fd78b04c4b49a" }))));
|
|
5965
5909
|
}
|
|
5966
5910
|
componentDidRender() {
|
|
5967
5911
|
if (this.files.length === 0) {
|
data/input-attachment/README.md
CHANGED
|
@@ -159,20 +159,64 @@ checkValidity(): boolean
|
|
|
159
159
|
|
|
160
160
|
## Styling
|
|
161
161
|
|
|
162
|
-
The
|
|
162
|
+
The components use Shadow DOM, so external CSS cannot reach their internals. Instead, all visual properties are exposed as CSS custom properties that pierce the shadow boundary. Set them on the `input-attachment` element or any ancestor.
|
|
163
|
+
|
|
164
|
+
### CSS Custom Properties
|
|
165
|
+
|
|
166
|
+
#### Text & Drop Zone
|
|
167
|
+
|
|
168
|
+
| Property | Default | Description |
|
|
169
|
+
| -------- | ------- | ----------- |
|
|
170
|
+
| `--input-attachment-text-color` | `#000` | Main text color |
|
|
171
|
+
| `--input-attachment-drop-bg` | `rgba(255,255,255, 0.25)` | Drop zone background |
|
|
172
|
+
| `--input-attachment-drop-border` | `rgba(0,0,0, 0.25)` | Drop zone dashed border |
|
|
173
|
+
| `--input-attachment-drop-color` | `#444` | Drop zone text color |
|
|
174
|
+
| `--input-attachment-drop-bg-active` | `rgba(255,255,255, 0.5)` | Drop zone background during drag-over |
|
|
175
|
+
|
|
176
|
+
#### Upload Dialog
|
|
177
|
+
|
|
178
|
+
| Property | Default | Description |
|
|
179
|
+
| -------- | ------- | ----------- |
|
|
180
|
+
| `--input-attachment-overlay-bg` | `rgba(51, 51, 51, 0.9)` | Modal overlay background |
|
|
181
|
+
| `--input-attachment-dialog-bg` | `#fcfcfc` | Dialog content background |
|
|
182
|
+
| `--input-attachment-dialog-border` | `#1f1f1f` | Dialog heading border |
|
|
183
|
+
|
|
184
|
+
#### Errors
|
|
185
|
+
|
|
186
|
+
| Property | Default | Description |
|
|
187
|
+
| -------- | ------- | ----------- |
|
|
188
|
+
| `--input-attachment-error-color` | `#c00` | Validation error and retry link text |
|
|
189
|
+
| `--input-attachment-error-color-hover` | `#900` | Retry link hover color |
|
|
190
|
+
| `--input-attachment-error-bg` | `rgba(74, 70, 70, 0.25)` | Error state progress bar background |
|
|
191
|
+
|
|
192
|
+
### Dark Mode
|
|
193
|
+
|
|
194
|
+
The components automatically adapt to dark color schemes via `prefers-color-scheme: dark`. No configuration is needed — if the user's OS or browser is set to dark mode, the components will use appropriate colors.
|
|
195
|
+
|
|
196
|
+
To manually apply a dark theme (e.g., for a toggle-based dark mode), set the custom properties yourself:
|
|
163
197
|
|
|
164
198
|
```css
|
|
165
|
-
input-attachment {
|
|
166
|
-
--input-attachment-text-color: #
|
|
199
|
+
.dark-theme input-attachment {
|
|
200
|
+
--input-attachment-text-color: #e0e0e0;
|
|
201
|
+
--input-attachment-drop-bg: rgba(255,255,255, 0.08);
|
|
202
|
+
--input-attachment-drop-border: rgba(255,255,255, 0.25);
|
|
203
|
+
--input-attachment-drop-color: #bbb;
|
|
204
|
+
--input-attachment-drop-bg-active: rgba(255,255,255, 0.15);
|
|
205
|
+
--input-attachment-dialog-bg: #2a2a2a;
|
|
206
|
+
--input-attachment-dialog-border: #555;
|
|
207
|
+
--input-attachment-error-color: #f66;
|
|
208
|
+
--input-attachment-error-color-hover: #f99;
|
|
209
|
+
--input-attachment-error-bg: rgba(200, 100, 100, 0.2);
|
|
167
210
|
}
|
|
168
211
|
```
|
|
169
212
|
|
|
170
|
-
|
|
213
|
+
### Parts
|
|
214
|
+
|
|
215
|
+
Style the file-drop title using the `::part()` pseudo-element:
|
|
171
216
|
|
|
172
217
|
```css
|
|
173
218
|
input-attachment::part(title) {
|
|
174
219
|
font-size: 16px;
|
|
175
|
-
color: #333;
|
|
176
220
|
}
|
|
177
221
|
```
|
|
178
222
|
|
data/input-attachment/bun.lockb
CHANGED
|
Binary file
|
|
@@ -49,8 +49,7 @@ progress-bar.complete + .progress-icon{
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
progress-bar.error{
|
|
52
|
-
background:
|
|
53
|
-
background: rgba(74, 70, 70, 0.25);
|
|
52
|
+
background: var(--input-attachment-error-bg, rgba(74, 70, 70, 0.25));
|
|
54
53
|
opacity: 1;
|
|
55
54
|
}
|
|
56
55
|
|
|
@@ -83,7 +82,7 @@ progress-bar.error{
|
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
.retry-media{
|
|
86
|
-
color: #c00;
|
|
85
|
+
color: var(--input-attachment-error-color, #c00);
|
|
87
86
|
font-size: 12px;
|
|
88
87
|
text-decoration: underline;
|
|
89
88
|
cursor: pointer;
|
|
@@ -91,11 +90,23 @@ progress-bar.error{
|
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
.retry-media:hover{
|
|
94
|
-
color: #900;
|
|
93
|
+
color: var(--input-attachment-error-color-hover, #900);
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
.validation-error{
|
|
98
|
-
color: #c00;
|
|
97
|
+
color: var(--input-attachment-error-color, #c00);
|
|
99
98
|
font-size: 12px;
|
|
100
99
|
margin: 4px 0 0 10px;
|
|
101
100
|
}
|
|
101
|
+
|
|
102
|
+
@media (prefers-color-scheme: dark) {
|
|
103
|
+
:host {
|
|
104
|
+
--input-attachment-error-color: #f66;
|
|
105
|
+
--input-attachment-error-color-hover: #f99;
|
|
106
|
+
--input-attachment-error-bg: rgba(200, 100, 100, 0.2);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.remove-media {
|
|
110
|
+
filter: invert(1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -14,12 +14,12 @@ file-drop {
|
|
|
14
14
|
cursor: pointer;
|
|
15
15
|
display: block;
|
|
16
16
|
outline-offset: -10px;
|
|
17
|
-
background: rgba(255,255,255, 0.25);
|
|
17
|
+
background: var(--input-attachment-drop-bg, rgba(255,255,255, 0.25));
|
|
18
18
|
padding: 20px;
|
|
19
19
|
text-align: center;
|
|
20
20
|
transition: all 0.15s;
|
|
21
|
-
outline: 2px dashed rgba(0,0,0,0.25);
|
|
22
|
-
color: #444;
|
|
21
|
+
outline: 2px dashed var(--input-attachment-drop-border, rgba(0,0,0,0.25));
|
|
22
|
+
color: var(--input-attachment-drop-color, #444);
|
|
23
23
|
font-size: 14px;
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -33,8 +33,8 @@ p{
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
.-dragover{
|
|
36
|
-
background: rgba(255,255,255,0.5);
|
|
37
|
-
outline: 2px dashed rgba(0,0,0,0.25);
|
|
36
|
+
background: var(--input-attachment-drop-bg-active, rgba(255,255,255,0.5));
|
|
37
|
+
outline: 2px dashed var(--input-attachment-drop-border, rgba(0,0,0,0.25));
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
.media-preview {
|
|
@@ -56,18 +56,19 @@ p{
|
|
|
56
56
|
display: flex;
|
|
57
57
|
align-items: center;
|
|
58
58
|
justify-content: center;
|
|
59
|
-
background: rgba(#333, 0.9);
|
|
59
|
+
background: var(--input-attachment-overlay-bg, rgba(#333, 0.9));
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
.direct-upload-content{
|
|
63
63
|
display: block;
|
|
64
|
-
background: #fcfcfc;
|
|
64
|
+
background: var(--input-attachment-dialog-bg, #fcfcfc);
|
|
65
|
+
color: var(--input-attachment-text-color, #000);
|
|
65
66
|
padding: 40px 60px 60px;
|
|
66
67
|
border-radius: 3px;
|
|
67
68
|
width: 60vw;
|
|
68
69
|
}
|
|
69
70
|
.direct-upload-content h3{
|
|
70
|
-
border-bottom: 2px solid #1f1f1f;
|
|
71
|
+
border-bottom: 2px solid var(--input-attachment-dialog-border, #1f1f1f);
|
|
71
72
|
margin-bottom: 20px;
|
|
72
73
|
}
|
|
73
74
|
|
|
@@ -86,7 +87,7 @@ p{
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
.direct-upload--error{
|
|
89
|
-
border-color: red;
|
|
90
|
+
border-color: var(--input-attachment-error-color, red);
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
input[type=file][data-direct-upload-url][disabled]{
|
|
@@ -98,3 +99,16 @@ input[type=file][data-direct-upload-url][disabled]{
|
|
|
98
99
|
margin-top: 10px;
|
|
99
100
|
font-size: 0.9em;
|
|
100
101
|
}
|
|
102
|
+
|
|
103
|
+
@media (prefers-color-scheme: dark) {
|
|
104
|
+
:host {
|
|
105
|
+
--input-attachment-text-color: #e0e0e0;
|
|
106
|
+
--input-attachment-drop-bg: rgba(255,255,255, 0.08);
|
|
107
|
+
--input-attachment-drop-border: rgba(255,255,255, 0.25);
|
|
108
|
+
--input-attachment-drop-color: #bbb;
|
|
109
|
+
--input-attachment-drop-bg-active: rgba(255,255,255, 0.15);
|
|
110
|
+
--input-attachment-dialog-bg: #2a2a2a;
|
|
111
|
+
--input-attachment-dialog-border: #555;
|
|
112
|
+
--input-attachment-error-color: #f66;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -46,65 +46,15 @@ export class InputAttachment {
|
|
|
46
46
|
const existingFiles = Array.from(this.el.children).filter(e => e.tagName == "ATTACHMENT-FILE");
|
|
47
47
|
if(existingFiles.length > 0) this.files = existingFiles
|
|
48
48
|
|
|
49
|
-
// Restore from localStorage BEFORE setting initial validity (which triggers saveToLocalStorage)
|
|
50
|
-
this.restoreFromLocalStorage()
|
|
51
|
-
|
|
52
49
|
// Set initial validity state (only if we have files from above)
|
|
53
50
|
if(this.files.length > 0) this.updateFormValue()
|
|
54
51
|
}
|
|
55
52
|
|
|
56
53
|
componentDidLoad() {
|
|
57
|
-
// Clear persistence data when form is submitted
|
|
58
|
-
if (this.form) {
|
|
59
|
-
this.form.addEventListener('submit', () => this.clearLocalStorage())
|
|
60
|
-
}
|
|
61
|
-
|
|
62
54
|
// Listen for file input changes directly (JSX onChange doesn't reliably work in shadow DOM)
|
|
63
55
|
this.fileInput?.addEventListener('change', this.handleFileInputChange)
|
|
64
56
|
}
|
|
65
57
|
|
|
66
|
-
get localStorageKey() {
|
|
67
|
-
if (!this.form || !this.name) return null
|
|
68
|
-
const formId = this.form.id || this.form.action || 'form'
|
|
69
|
-
return `input-attachment:${formId}:${this.name}`
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
saveToLocalStorage() {
|
|
73
|
-
const key = this.localStorageKey
|
|
74
|
-
if (!key) return
|
|
75
|
-
|
|
76
|
-
const data = this.persistenceData
|
|
77
|
-
if (data.length > 0) {
|
|
78
|
-
localStorage.setItem(key, JSON.stringify(data))
|
|
79
|
-
} else {
|
|
80
|
-
localStorage.removeItem(key)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
restoreFromLocalStorage() {
|
|
85
|
-
const key = this.localStorageKey
|
|
86
|
-
if (!key || this.files.length > 0) return
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
const stored = localStorage.getItem(key)
|
|
90
|
-
if (stored) {
|
|
91
|
-
const data = JSON.parse(stored)
|
|
92
|
-
if (Array.isArray(data) && data.length > 0) {
|
|
93
|
-
this.restoreFromPersistence(data)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
} catch (e) {
|
|
97
|
-
// Invalid JSON, ignore
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
clearLocalStorage() {
|
|
102
|
-
const key = this.localStorageKey
|
|
103
|
-
if (key) {
|
|
104
|
-
localStorage.removeItem(key)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
58
|
// Methods
|
|
109
59
|
|
|
110
60
|
get files() {
|
|
@@ -119,25 +69,7 @@ export class InputAttachment {
|
|
|
119
69
|
}
|
|
120
70
|
|
|
121
71
|
get value() {
|
|
122
|
-
return this.files.map(
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
set value(val) {
|
|
126
|
-
const newValue = val || []
|
|
127
|
-
if(JSON.stringify(this.value) !== JSON.stringify(newValue)) { // this is insane. javascript is fucking garbage.
|
|
128
|
-
this.files = newValue.map(signedId => {
|
|
129
|
-
const attachmentFile = document.createElement('attachment-file') as any
|
|
130
|
-
attachmentFile.name = this.name
|
|
131
|
-
attachmentFile.preview = this.preview
|
|
132
|
-
attachmentFile.signedId = signedId
|
|
133
|
-
return attachmentFile
|
|
134
|
-
})
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// For form-persistence: store complete attachment data (not just signed_ids)
|
|
139
|
-
get persistenceData() {
|
|
140
|
-
return this.files.map(f => ({
|
|
72
|
+
return JSON.stringify(this.files.map(f => ({
|
|
141
73
|
value: f.value,
|
|
142
74
|
filename: f.filename,
|
|
143
75
|
src: f.src,
|
|
@@ -145,12 +77,15 @@ export class InputAttachment {
|
|
|
145
77
|
percent: f.percent,
|
|
146
78
|
size: f.size,
|
|
147
79
|
filetype: f.filetype,
|
|
148
|
-
}))
|
|
80
|
+
})))
|
|
149
81
|
}
|
|
150
82
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
83
|
+
set value(val) {
|
|
84
|
+
const data = JSON.parse(val || "[]")
|
|
85
|
+
if (data.length === 0) {
|
|
86
|
+
this.files = []
|
|
87
|
+
return
|
|
88
|
+
}
|
|
154
89
|
this.files = data.map(item => {
|
|
155
90
|
const attachmentFile = document.createElement('attachment-file') as any
|
|
156
91
|
attachmentFile.name = this.name
|
|
@@ -170,7 +105,7 @@ export class InputAttachment {
|
|
|
170
105
|
updateFormValue() {
|
|
171
106
|
if (!this.name || !this.internals?.setFormValue) return
|
|
172
107
|
const formData = new FormData()
|
|
173
|
-
const values = this.value.filter(v => v)
|
|
108
|
+
const values = this.files.map(f => f.value).filter(v => v)
|
|
174
109
|
if (this.multiple) {
|
|
175
110
|
// For has_many_attached: append each signed_id separately
|
|
176
111
|
values.forEach(signedId => formData.append(this.name, signedId))
|
|
@@ -182,9 +117,6 @@ export class InputAttachment {
|
|
|
182
117
|
}
|
|
183
118
|
this.internals.setFormValue(formData)
|
|
184
119
|
|
|
185
|
-
// Save to localStorage for persistence across page reloads
|
|
186
|
-
this.saveToLocalStorage()
|
|
187
|
-
|
|
188
120
|
// Update validity state - check for required and child validation errors
|
|
189
121
|
if (this.required && this.files.length === 0) {
|
|
190
122
|
this.internals.setValidity({ valueMissing: true }, "Please select a file.", this.fileInput)
|
|
@@ -202,8 +134,7 @@ export class InputAttachment {
|
|
|
202
134
|
}
|
|
203
135
|
|
|
204
136
|
reset() {
|
|
205
|
-
this.
|
|
206
|
-
this.clearLocalStorage()
|
|
137
|
+
this.files = []
|
|
207
138
|
}
|
|
208
139
|
|
|
209
140
|
handleFileInputChange = () => {
|
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.4.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-03-
|
|
11
|
+
date: 2026-03-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activestorage
|