bard-attachment_field 0.4.1 → 0.4.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: 455080b5539151ab0d19ff817c8a8ca1c81af69abc7de283f61ee6753f94cdc2
4
- data.tar.gz: 3bc698d1aca4b7cdbe50dab75758bdf65b2a818fa8d3a3ce17651d7ed97a7ced
3
+ metadata.gz: c1aa22fabe0aa12db6b1ec56aab073e7bd350d6f3d7446937101273f8a1d4f4e
4
+ data.tar.gz: 9493db850d44b78eaeb0ef3813a833c51d22afd1a480eb807ed31bc1dde0bc7a
5
5
  SHA512:
6
- metadata.gz: a5625056035d5e5aa4e1d51de78a21081f3b5e233db5c1582167537f936d904c5cc70070d55a234b308958cc25da90588c0a1543e779613b70bd091d10dbf8b7
7
- data.tar.gz: e860eb4c5f28cd7b1c4519cbc06dc3d227c5dcb0b2cf4a3bc6d74aae77d9709dc5f0d418a04c3965a411ec5edb17d1c03df24aef0e491c346a40bd83be05d641
6
+ metadata.gz: 01b4bd7a80b384815ef381193141483910df60280208c5501a35004d94c8f148eda0c5384e58c59fe469381d6711f48e7cb132d412de5bb2043819e995171535
7
+ data.tar.gz: f5a453ed7488d43301f659f0388e05e04ce262ea16707a9ab3f335907dd1f051f23295c5c22fdad4a39d6262927c1998d718e33759bf7592d1573fd70fa82f41
@@ -5436,35 +5436,108 @@ var AttachmentFile3 = AttachmentFile;
5436
5436
  // dist/components/attachment-preview.js
5437
5437
  var AttachmentPreview3 = AttachmentPreview;
5438
5438
 
5439
+ // dist/components/index2.js
5440
+ var ProgressBar = class extends HTMLElement {
5441
+ constructor() {
5442
+ super();
5443
+ this.attachShadow({ mode: "open" });
5444
+ this._percent = 0;
5445
+ }
5446
+ connectedCallback() {
5447
+ this.render();
5448
+ this.updateBar();
5449
+ }
5450
+ get percent() {
5451
+ return this._percent;
5452
+ }
5453
+ set percent(value) {
5454
+ this._percent = Number(value) || 0;
5455
+ this.setAttribute("percent", this._percent);
5456
+ this.updateBar();
5457
+ }
5458
+ static get observedAttributes() {
5459
+ return ["percent"];
5460
+ }
5461
+ attributeChangedCallback(name, oldValue, newValue) {
5462
+ if (name === "percent") {
5463
+ this._percent = Number(newValue) || 0;
5464
+ this.updateBar();
5465
+ }
5466
+ }
5467
+ updateBar() {
5468
+ const bar = this.shadowRoot?.querySelector(".bar");
5469
+ if (bar) {
5470
+ bar.style.width = `${this._percent}%`;
5471
+ }
5472
+ }
5473
+ render() {
5474
+ this.shadowRoot.innerHTML = `
5475
+ <style>
5476
+ :host {
5477
+ --progress-color: rgb(57, 137, 39);
5478
+ --progress-duration: 120ms;
5479
+ --bar-height: 32px;
5480
+ --bar-radius: 4px;
5481
+ --bar-padding: 8px;
5482
+ --bar-border-color: #999;
5483
+
5484
+ display: block;
5485
+ position: relative;
5486
+ padding: var(--bar-padding);
5487
+ border: 1px solid var(--bar-border-color);
5488
+ border-radius: var(--bar-radius);
5489
+ }
5490
+
5491
+ .bar {
5492
+ position: absolute;
5493
+ top: 0;
5494
+ left: 0;
5495
+ height: 100%;
5496
+ background: var(--progress-color);
5497
+ width: 0%;
5498
+ transition: width var(--progress-duration) ease, opacity 60ms ease;
5499
+ border-radius: var(--bar-radius);
5500
+ }
5501
+
5502
+ .content {
5503
+ position: relative;
5504
+ display: block;
5505
+ color: white;
5506
+ font-size: 13px;
5507
+ z-index: 1;
5508
+ }
5509
+ </style>
5510
+
5511
+ <div class="bar"></div>
5512
+ <span class="content">
5513
+ <slot></slot>
5514
+ </span>
5515
+ `;
5516
+ }
5517
+ };
5518
+ if (!customElements.get("progress-bar")) {
5519
+ customElements.define("progress-bar", ProgressBar);
5520
+ }
5521
+
5439
5522
  // dist/components/input-attachment.js
5440
5523
  var FormController = class _FormController {
5441
5524
  static instance(form, options = {}) {
5442
5525
  return form.inputAttachmentFormController ||= new _FormController(form, options);
5443
5526
  }
5444
- progressContainerTarget;
5445
5527
  dialog;
5446
5528
  element;
5447
- progressTargetMap;
5448
5529
  controllers;
5449
5530
  submitted;
5450
5531
  processing;
5451
5532
  constructor(form, { uploadDialog = true } = {}) {
5452
5533
  this.element = form;
5453
- this.progressTargetMap = {};
5454
5534
  this.controllers = [];
5455
5535
  this.submitted = false;
5456
5536
  this.processing = false;
5457
5537
  if (uploadDialog) {
5458
- this.element.insertAdjacentHTML("beforeend", `<dialog id="form-controller-dialog">
5459
- <div class="direct-upload-wrapper">
5460
- <div class="direct-upload-content">
5461
- <h3>Uploading your media</h3>
5462
- <div id="progress-container"></div>
5463
- </div>
5464
- </div>
5465
- </dialog>`);
5466
- this.dialog = this.element.querySelector("#form-controller-dialog");
5467
- this.progressContainerTarget = this.dialog.querySelector("#progress-container");
5538
+ this.dialog = document.createElement("upload-dialog");
5539
+ this.dialog.id = "form-controller-dialog";
5540
+ this.element.appendChild(this.dialog);
5468
5541
  }
5469
5542
  this.element.addEventListener("submit", (event) => this.submit(event));
5470
5543
  window.addEventListener("beforeunload", (event) => this.beforeUnload(event));
@@ -5489,7 +5562,7 @@ var FormController = class _FormController {
5489
5562
  this.setInputAttachmentsDisabled(true);
5490
5563
  this.startNextController();
5491
5564
  if (this.processing) {
5492
- this.dialog?.showModal();
5565
+ this.dialog?.open();
5493
5566
  }
5494
5567
  }
5495
5568
  startNextController() {
@@ -5547,42 +5620,30 @@ var FormController = class _FormController {
5547
5620
  }
5548
5621
  init(event) {
5549
5622
  const { id: id2, file, controller } = event.detail;
5550
- if (this.progressContainerTarget) {
5551
- this.progressContainerTarget.insertAdjacentHTML("beforebegin", `
5552
- <progress-bar id="direct-upload-${id2}" class="direct-upload--pending">${file?.name || "Uploading..."}</progress-bar>
5553
- `);
5554
- this.progressTargetMap[id2] = document.getElementById(`direct-upload-${id2}`);
5555
- }
5623
+ this.dialog?.addUpload(`direct-upload-${id2}`, file?.name || "Uploading...");
5556
5624
  this.controllers.push(controller);
5557
5625
  this.startNextController();
5558
5626
  }
5559
5627
  start(event) {
5560
- this.progressTargetMap[event.detail.id]?.classList.remove("direct-upload--pending");
5628
+ this.dialog?.startUpload(`direct-upload-${event.detail.id}`);
5561
5629
  }
5562
5630
  progress(event) {
5563
5631
  const { id: id2, progress } = event.detail;
5564
- const target = this.progressTargetMap[id2];
5565
- if (target)
5566
- target.percent = progress;
5632
+ this.dialog?.updateProgress(`direct-upload-${id2}`, progress);
5567
5633
  }
5568
5634
  error(event) {
5569
5635
  event.preventDefault();
5570
5636
  const { id: id2, error } = event.detail;
5571
- const target = this.progressTargetMap[id2];
5572
- if (target) {
5573
- target.classList.add("direct-upload--error");
5574
- target.title = error;
5575
- }
5637
+ this.dialog?.setError(`direct-upload-${id2}`, error);
5576
5638
  }
5577
5639
  end(event) {
5578
- this.progressTargetMap[event.detail.id]?.classList.add("direct-upload--complete");
5640
+ this.dialog?.completeUpload(`direct-upload-${event.detail.id}`);
5579
5641
  }
5580
5642
  removeUploadedFile(event) {
5581
5643
  const uploadedFile = event.detail;
5582
5644
  const id2 = uploadedFile.controller?.directUpload?.id;
5583
5645
  if (id2) {
5584
- document.getElementById(`direct-upload-${id2}`)?.remove();
5585
- delete this.progressTargetMap[id2];
5646
+ this.dialog?.removeUpload(`direct-upload-${id2}`);
5586
5647
  }
5587
5648
  this.setInputAttachmentsDisabled(false);
5588
5649
  requestAnimationFrame(() => this.submitForm());
@@ -5673,88 +5734,7 @@ var FileDrop = class extends HTMLElement {
5673
5734
  if (!customElements.get("file-drop")) {
5674
5735
  customElements.define("file-drop", FileDrop);
5675
5736
  }
5676
- var ProgressBar = class extends HTMLElement {
5677
- constructor() {
5678
- super();
5679
- this.attachShadow({ mode: "open" });
5680
- this._percent = 0;
5681
- }
5682
- connectedCallback() {
5683
- this.render();
5684
- this.updateBar();
5685
- }
5686
- get percent() {
5687
- return this._percent;
5688
- }
5689
- set percent(value) {
5690
- this._percent = Number(value) || 0;
5691
- this.setAttribute("percent", this._percent);
5692
- this.updateBar();
5693
- }
5694
- static get observedAttributes() {
5695
- return ["percent"];
5696
- }
5697
- attributeChangedCallback(name, oldValue, newValue) {
5698
- if (name === "percent") {
5699
- this._percent = Number(newValue) || 0;
5700
- this.updateBar();
5701
- }
5702
- }
5703
- updateBar() {
5704
- const bar = this.shadowRoot?.querySelector(".bar");
5705
- if (bar) {
5706
- bar.style.width = `${this._percent}%`;
5707
- }
5708
- }
5709
- render() {
5710
- this.shadowRoot.innerHTML = `
5711
- <style>
5712
- :host {
5713
- --progress-color: rgb(57, 137, 39);
5714
- --progress-duration: 120ms;
5715
- --bar-height: 32px;
5716
- --bar-radius: 4px;
5717
- --bar-padding: 8px;
5718
- --bar-border-color: #999;
5719
-
5720
- display: block;
5721
- position: relative;
5722
- padding: var(--bar-padding);
5723
- border: 1px solid var(--bar-border-color);
5724
- border-radius: var(--bar-radius);
5725
- }
5726
-
5727
- .bar {
5728
- position: absolute;
5729
- top: 0;
5730
- left: 0;
5731
- height: 100%;
5732
- background: var(--progress-color);
5733
- width: 0%;
5734
- transition: width var(--progress-duration) ease, opacity 60ms ease;
5735
- border-radius: var(--bar-radius);
5736
- }
5737
-
5738
- .content {
5739
- position: relative;
5740
- display: block;
5741
- color: white;
5742
- font-size: 13px;
5743
- z-index: 1;
5744
- }
5745
- </style>
5746
-
5747
- <div class="bar"></div>
5748
- <span class="content">
5749
- <slot></slot>
5750
- </span>
5751
- `;
5752
- }
5753
- };
5754
- if (!customElements.get("progress-bar")) {
5755
- customElements.define("progress-bar", ProgressBar);
5756
- }
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}}";
5737
+ 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}: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-error-color:#f66}}";
5758
5738
  var InputAttachment$1 = /* @__PURE__ */ proxyCustomElement(class InputAttachment extends H {
5759
5739
  get el() {
5760
5740
  return this;
@@ -5900,12 +5880,12 @@ var InputAttachment$1 = /* @__PURE__ */ proxyCustomElement(class InputAttachment
5900
5880
  return this.disabled || !!this.el.closest("fieldset[disabled]");
5901
5881
  }
5902
5882
  render() {
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: {
5883
+ return h(Host, { key: "6a45fb4254e303d6bdc0e904129d5db9f94799b3" }, h("input", { key: "68df3d0b0b7ea59551a6a2ef78a59f0ac77cb848", 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: {
5904
5884
  opacity: "0.01",
5905
5885
  width: "1px",
5906
5886
  height: "1px",
5907
5887
  zIndex: "-999"
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" }))));
5888
+ } }), h("file-drop", { key: "4af1e60084b31e232bbda03a540d0a87e301ee1e", onClick: () => this.fileInput?.click(), onDrop: this.handleDrop }, h("p", { key: "c714acafc22ec669b2978065b04559cd0f097e57", part: "title" }, h("strong", { key: "3fab310e5ee4e1d8ceb65076a88f67a9a51a3a4d" }, "Choose ", this.multiple ? "files" : "file", " "), h("span", { key: "694f5681825da6005f65d6f8038fb75170b5dfca" }, "or drag ", this.multiple ? "them" : "it", " here.")), h("div", { key: "8a0d60e85928524d2adbcf58b24bbce095cc9a11", class: `media-preview ${this.multiple ? "-stacked" : ""}` }, h("slot", { key: "5b8ee7c5eb04099451d2d5059095d62b39b430b5" }))));
5909
5889
  }
5910
5890
  componentDidRender() {
5911
5891
  if (this.files.length === 0) {
@@ -5986,13 +5966,80 @@ var InputAttachment$1 = /* @__PURE__ */ proxyCustomElement(class InputAttachment
5986
5966
  }, [[0, "attachment-file:remove", "removeUploadedFile"], [0, "attachment-file:validation", "handleChildValidation"], [0, "attachment-file:ready", "handleChildReady"], [0, "direct-upload:end", "fireChangeEvent"]]]);
5987
5967
  var InputAttachment2 = InputAttachment$1;
5988
5968
 
5969
+ // dist/components/upload-dialog.js
5970
+ var uploadDialogCss = "dialog{border:none;padding:0;background:transparent;max-width:100vw;max-height:100vh}dialog::backdrop{background:transparent}.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(51, 51, 51, 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}.direct-upload--pending{opacity:0.6}.direct-upload--complete{opacity:0.4}.direct-upload--error{border-color:var(--input-attachment-error-color, red)}@media (prefers-color-scheme: dark){:host{--input-attachment-dialog-bg:#2a2a2a;--input-attachment-dialog-border:#555;--input-attachment-error-color:#f66}}";
5971
+ var UploadDialog$1 = /* @__PURE__ */ proxyCustomElement(class UploadDialog extends H {
5972
+ constructor(registerHost2) {
5973
+ super();
5974
+ if (registerHost2 !== false) {
5975
+ this.__registerHost();
5976
+ }
5977
+ this.__attachShadow();
5978
+ }
5979
+ dialog;
5980
+ uploads = [];
5981
+ async open() {
5982
+ this.dialog.showModal();
5983
+ }
5984
+ async close() {
5985
+ this.dialog.close();
5986
+ }
5987
+ async addUpload(id2, filename) {
5988
+ this.uploads = [...this.uploads, {
5989
+ id: id2,
5990
+ filename,
5991
+ pending: true,
5992
+ percent: 0,
5993
+ complete: false,
5994
+ error: null
5995
+ }];
5996
+ }
5997
+ async startUpload(id2) {
5998
+ this.uploads = this.uploads.map((u) => u.id === id2 ? { ...u, pending: false } : u);
5999
+ }
6000
+ async updateProgress(id2, percent) {
6001
+ this.uploads = this.uploads.map((u) => u.id === id2 ? { ...u, percent } : u);
6002
+ }
6003
+ async setError(id2, error) {
6004
+ this.uploads = this.uploads.map((u) => u.id === id2 ? { ...u, error } : u);
6005
+ }
6006
+ async completeUpload(id2) {
6007
+ this.uploads = this.uploads.map((u) => u.id === id2 ? { ...u, complete: true } : u);
6008
+ }
6009
+ async removeUpload(id2) {
6010
+ this.uploads = this.uploads.filter((u) => u.id !== id2);
6011
+ }
6012
+ render() {
6013
+ return h(Host, { key: "76874b06863ae2cab526de1f0e491bb04300c6fc" }, h("dialog", { key: "55b715d6d5fc6ee0c6a16df16c6374ca6e285aee", ref: (el) => this.dialog = el }, h("div", { key: "bab0ae4ed41bb5a909351866d92f0da330858691", class: "direct-upload-wrapper" }, h("div", { key: "23fe0a67bd942f5f4a53df1bd61bec8863104b2c", class: "direct-upload-content" }, h("h3", { key: "d2e349d855c046a541c9dc3ac837873bcd75da22" }, "Uploading your media"), this.uploads.map((upload) => h("progress-bar", { key: upload.id, class: {
6014
+ "direct-upload--pending": upload.pending,
6015
+ "direct-upload--complete": upload.complete,
6016
+ "direct-upload--error": !!upload.error
6017
+ }, percent: upload.percent, title: upload.error || void 0 }, upload.filename))))));
6018
+ }
6019
+ static get style() {
6020
+ return uploadDialogCss;
6021
+ }
6022
+ }, [257, "upload-dialog", {
6023
+ "uploads": [32],
6024
+ "open": [64],
6025
+ "close": [64],
6026
+ "addUpload": [64],
6027
+ "startUpload": [64],
6028
+ "updateProgress": [64],
6029
+ "setError": [64],
6030
+ "completeUpload": [64],
6031
+ "removeUpload": [64]
6032
+ }]);
6033
+ var UploadDialog2 = UploadDialog$1;
6034
+
5989
6035
  // dist/components/index.js
5990
6036
  var defineCustomElements = (opts) => {
5991
6037
  if (typeof customElements !== "undefined") {
5992
6038
  [
5993
6039
  AttachmentFile3,
5994
6040
  AttachmentPreview3,
5995
- InputAttachment2
6041
+ InputAttachment2,
6042
+ UploadDialog2
5996
6043
  ].forEach((cmp) => {
5997
6044
  if (!customElements.get(cmp.is)) {
5998
6045
  customElements.define(cmp.is, cmp, opts);
@@ -12,24 +12,21 @@ afterEach(() => {
12
12
 
13
13
  describe("FormController", () => {
14
14
  describe("default (uploadDialog: true)", () => {
15
- it("creates the dialog and progress container", () => {
15
+ it("creates the upload-dialog element", () => {
16
16
  const form = createForm()
17
17
  const controller = FormController.instance(form)
18
18
  expect(controller.dialog).toBeTruthy()
19
- expect(controller.progressContainerTarget).toBeTruthy()
19
+ expect(controller.dialog.tagName.toLowerCase()).toBe("upload-dialog")
20
20
  expect(form.querySelector("#form-controller-dialog")).toBeTruthy()
21
- expect(form.querySelector("#progress-container")).toBeTruthy()
22
21
  })
23
22
  })
24
23
 
25
24
  describe("uploadDialog: false", () => {
26
- it("does not create the dialog or progress container", () => {
25
+ it("does not create the dialog", () => {
27
26
  const form = createForm()
28
27
  const controller = FormController.instance(form, { uploadDialog: false })
29
28
  expect(controller.dialog).toBeFalsy()
30
- expect(controller.progressContainerTarget).toBeFalsy()
31
29
  expect(form.querySelector("#form-controller-dialog")).toBeNull()
32
- expect(form.querySelector("#progress-container")).toBeNull()
33
30
  })
34
31
 
35
32
  it("still registers event listeners and queues controllers", () => {
@@ -1,39 +1,28 @@
1
1
  import DirectUploadController from "../attachment-file/direct-upload-controller"
2
+ import "../upload-dialog/upload-dialog"
2
3
 
3
4
  export default class FormController {
4
5
  static instance(form, options = {}) {
5
6
  return form.inputAttachmentFormController ||= new FormController(form, options)
6
7
  }
7
8
 
8
- progressContainerTarget: HTMLElement
9
- dialog: HTMLDialogElement
9
+ dialog: HTMLElement
10
10
 
11
11
  element: HTMLFormElement
12
- progressTargetMap: {}
13
12
  controllers: Array<DirectUploadController>
14
13
  submitted: boolean
15
14
  processing: boolean
16
15
 
17
16
  constructor(form, { uploadDialog = true } = {}) {
18
17
  this.element = form
19
- this.progressTargetMap = {}
20
18
  this.controllers = []
21
19
  this.submitted = false
22
20
  this.processing = false
23
21
 
24
22
  if (uploadDialog) {
25
- this.element.insertAdjacentHTML("beforeend",
26
- `<dialog id="form-controller-dialog">
27
- <div class="direct-upload-wrapper">
28
- <div class="direct-upload-content">
29
- <h3>Uploading your media</h3>
30
- <div id="progress-container"></div>
31
- </div>
32
- </div>
33
- </dialog>`)
34
-
35
- this.dialog = this.element.querySelector("#form-controller-dialog")
36
- this.progressContainerTarget = this.dialog.querySelector("#progress-container")
23
+ this.dialog = document.createElement("upload-dialog")
24
+ this.dialog.id = "form-controller-dialog"
25
+ this.element.appendChild(this.dialog)
37
26
  }
38
27
 
39
28
  this.element.addEventListener("submit", event => this.submit(event))
@@ -62,7 +51,7 @@ export default class FormController {
62
51
  this.setInputAttachmentsDisabled(true)
63
52
  this.startNextController()
64
53
  if(this.processing) {
65
- this.dialog?.showModal()
54
+ (this.dialog as any)?.open()
66
55
  }
67
56
  }
68
57
 
@@ -101,7 +90,7 @@ export default class FormController {
101
90
  submitForm() {
102
91
  if(!this.submitted) return
103
92
  if(this.hasUploadErrors()) {
104
- this.dialog?.close()
93
+ (this.dialog as any)?.close()
105
94
  this.setInputAttachmentsDisabled(false)
106
95
  return
107
96
  }
@@ -127,48 +116,35 @@ export default class FormController {
127
116
 
128
117
  init(event) {
129
118
  const { id, file, controller } = event.detail
130
-
131
- if (this.progressContainerTarget) {
132
- this.progressContainerTarget.insertAdjacentHTML("beforebegin", `
133
- <progress-bar id="direct-upload-${id}" class="direct-upload--pending">${file?.name || 'Uploading...'}</progress-bar>
134
- `)
135
- this.progressTargetMap[id] = document.getElementById(`direct-upload-${id}`)
136
- }
137
-
119
+ ;(this.dialog as any)?.addUpload(`direct-upload-${id}`, file?.name || "Uploading...")
138
120
  this.controllers.push(controller)
139
121
  this.startNextController()
140
122
  }
141
123
 
142
124
  start(event) {
143
- this.progressTargetMap[event.detail.id]?.classList.remove("direct-upload--pending")
125
+ ;(this.dialog as any)?.startUpload(`direct-upload-${event.detail.id}`)
144
126
  }
145
127
 
146
128
  progress(event) {
147
129
  const { id, progress } = event.detail
148
- const target = this.progressTargetMap[id]
149
- if (target) target.percent = progress
130
+ ;(this.dialog as any)?.updateProgress(`direct-upload-${id}`, progress)
150
131
  }
151
132
 
152
133
  error(event) {
153
134
  event.preventDefault()
154
135
  const { id, error } = event.detail
155
- const target = this.progressTargetMap[id]
156
- if (target) {
157
- target.classList.add("direct-upload--error")
158
- target.title = error
159
- }
136
+ ;(this.dialog as any)?.setError(`direct-upload-${id}`, error)
160
137
  }
161
138
 
162
139
  end(event) {
163
- this.progressTargetMap[event.detail.id]?.classList.add("direct-upload--complete")
140
+ ;(this.dialog as any)?.completeUpload(`direct-upload-${event.detail.id}`)
164
141
  }
165
142
 
166
143
  removeUploadedFile(event) {
167
144
  const uploadedFile = event.detail
168
145
  const id = uploadedFile.controller?.directUpload?.id
169
146
  if(id) {
170
- document.getElementById(`direct-upload-${id}`)?.remove()
171
- delete this.progressTargetMap[id]
147
+ (this.dialog as any)?.removeUpload(`direct-upload-${id}`)
172
148
  }
173
149
  this.setInputAttachmentsDisabled(false)
174
150
  requestAnimationFrame(() => this.submitForm())
@@ -44,56 +44,6 @@ p{
44
44
  justify-content: center;
45
45
  }
46
46
 
47
- // UPLOADER
48
-
49
- .direct-upload-wrapper{
50
- position: fixed;
51
- z-index: 9999;
52
- top: 0;
53
- left: 0;
54
- width: 100vw;
55
- height: 100vh;
56
- display: flex;
57
- align-items: center;
58
- justify-content: center;
59
- background: var(--input-attachment-overlay-bg, rgba(#333, 0.9));
60
- }
61
-
62
- .direct-upload-content{
63
- display: block;
64
- background: var(--input-attachment-dialog-bg, #fcfcfc);
65
- color: var(--input-attachment-text-color, #000);
66
- padding: 40px 60px 60px;
67
- border-radius: 3px;
68
- width: 60vw;
69
- }
70
- .direct-upload-content h3{
71
- border-bottom: 2px solid var(--input-attachment-dialog-border, #1f1f1f);
72
- margin-bottom: 20px;
73
- }
74
-
75
- .separate-upload{
76
- padding: 0 10px;
77
- margin-top: 10px;
78
- font-size: 0.9em;
79
- }
80
-
81
- .direct-upload--pending{
82
- opacity: 0.6;
83
- }
84
-
85
- .direct-upload--complete {
86
- opacity: 0.4;
87
- }
88
-
89
- .direct-upload--error{
90
- border-color: var(--input-attachment-error-color, red);
91
- }
92
-
93
- input[type=file][data-direct-upload-url][disabled]{
94
- display: none;
95
- }
96
-
97
47
  :host.separate-upload{
98
48
  padding: 0 10px;
99
49
  margin-top: 10px;
@@ -107,8 +57,6 @@ input[type=file][data-direct-upload-url][disabled]{
107
57
  --input-attachment-drop-border: rgba(255,255,255, 0.25);
108
58
  --input-attachment-drop-color: #bbb;
109
59
  --input-attachment-drop-bg-active: rgba(255,255,255, 0.15);
110
- --input-attachment-dialog-bg: #2a2a2a;
111
- --input-attachment-dialog-border: #555;
112
60
  --input-attachment-error-color: #f66;
113
61
  }
114
62
  }
@@ -0,0 +1,132 @@
1
+ # upload-dialog
2
+
3
+
4
+
5
+ <!-- Auto Generated Below -->
6
+
7
+
8
+ ## Methods
9
+
10
+ ### `addUpload(id: string, filename: string) => Promise<void>`
11
+
12
+
13
+
14
+ #### Parameters
15
+
16
+ | Name | Type | Description |
17
+ | ---------- | -------- | ----------- |
18
+ | `id` | `string` | |
19
+ | `filename` | `string` | |
20
+
21
+ #### Returns
22
+
23
+ Type: `Promise<void>`
24
+
25
+
26
+
27
+ ### `close() => Promise<void>`
28
+
29
+
30
+
31
+ #### Returns
32
+
33
+ Type: `Promise<void>`
34
+
35
+
36
+
37
+ ### `completeUpload(id: string) => Promise<void>`
38
+
39
+
40
+
41
+ #### Parameters
42
+
43
+ | Name | Type | Description |
44
+ | ---- | -------- | ----------- |
45
+ | `id` | `string` | |
46
+
47
+ #### Returns
48
+
49
+ Type: `Promise<void>`
50
+
51
+
52
+
53
+ ### `open() => Promise<void>`
54
+
55
+
56
+
57
+ #### Returns
58
+
59
+ Type: `Promise<void>`
60
+
61
+
62
+
63
+ ### `removeUpload(id: string) => Promise<void>`
64
+
65
+
66
+
67
+ #### Parameters
68
+
69
+ | Name | Type | Description |
70
+ | ---- | -------- | ----------- |
71
+ | `id` | `string` | |
72
+
73
+ #### Returns
74
+
75
+ Type: `Promise<void>`
76
+
77
+
78
+
79
+ ### `setError(id: string, error: string) => Promise<void>`
80
+
81
+
82
+
83
+ #### Parameters
84
+
85
+ | Name | Type | Description |
86
+ | ------- | -------- | ----------- |
87
+ | `id` | `string` | |
88
+ | `error` | `string` | |
89
+
90
+ #### Returns
91
+
92
+ Type: `Promise<void>`
93
+
94
+
95
+
96
+ ### `startUpload(id: string) => Promise<void>`
97
+
98
+
99
+
100
+ #### Parameters
101
+
102
+ | Name | Type | Description |
103
+ | ---- | -------- | ----------- |
104
+ | `id` | `string` | |
105
+
106
+ #### Returns
107
+
108
+ Type: `Promise<void>`
109
+
110
+
111
+
112
+ ### `updateProgress(id: string, percent: number) => Promise<void>`
113
+
114
+
115
+
116
+ #### Parameters
117
+
118
+ | Name | Type | Description |
119
+ | --------- | -------- | ----------- |
120
+ | `id` | `string` | |
121
+ | `percent` | `number` | |
122
+
123
+ #### Returns
124
+
125
+ Type: `Promise<void>`
126
+
127
+
128
+
129
+
130
+ ----------------------------------------------
131
+
132
+ *Built with [StencilJS](https://stenciljs.com/)*
@@ -0,0 +1,58 @@
1
+ dialog {
2
+ border: none;
3
+ padding: 0;
4
+ background: transparent;
5
+ max-width: 100vw;
6
+ max-height: 100vh;
7
+ }
8
+
9
+ dialog::backdrop {
10
+ background: transparent;
11
+ }
12
+
13
+ .direct-upload-wrapper {
14
+ position: fixed;
15
+ z-index: 9999;
16
+ top: 0;
17
+ left: 0;
18
+ width: 100vw;
19
+ height: 100vh;
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ background: var(--input-attachment-overlay-bg, rgba(51, 51, 51, 0.9));
24
+ }
25
+
26
+ .direct-upload-content {
27
+ display: block;
28
+ background: var(--input-attachment-dialog-bg, #fcfcfc);
29
+ color: var(--input-attachment-text-color, #000);
30
+ padding: 40px 60px 60px;
31
+ border-radius: 3px;
32
+ width: 60vw;
33
+ }
34
+
35
+ .direct-upload-content h3 {
36
+ border-bottom: 2px solid var(--input-attachment-dialog-border, #1f1f1f);
37
+ margin-bottom: 20px;
38
+ }
39
+
40
+ .direct-upload--pending {
41
+ opacity: 0.6;
42
+ }
43
+
44
+ .direct-upload--complete {
45
+ opacity: 0.4;
46
+ }
47
+
48
+ .direct-upload--error {
49
+ border-color: var(--input-attachment-error-color, red);
50
+ }
51
+
52
+ @media (prefers-color-scheme: dark) {
53
+ :host {
54
+ --input-attachment-dialog-bg: #2a2a2a;
55
+ --input-attachment-dialog-border: #555;
56
+ --input-attachment-error-color: #f66;
57
+ }
58
+ }
@@ -0,0 +1,105 @@
1
+ import { Component, Host, Method, State, h } from "@stencil/core";
2
+ import "@botandrose/progress-bar";
3
+
4
+ interface UploadEntry {
5
+ id: string;
6
+ filename: string;
7
+ pending: boolean;
8
+ percent: number;
9
+ complete: boolean;
10
+ error: string | null;
11
+ }
12
+
13
+ @Component({
14
+ tag: "upload-dialog",
15
+ styleUrl: "upload-dialog.css",
16
+ shadow: true,
17
+ })
18
+ export class UploadDialog {
19
+ private dialog: HTMLDialogElement;
20
+
21
+ @State() uploads: UploadEntry[] = [];
22
+
23
+ @Method()
24
+ async open() {
25
+ this.dialog.showModal();
26
+ }
27
+
28
+ @Method()
29
+ async close() {
30
+ this.dialog.close();
31
+ }
32
+
33
+ @Method()
34
+ async addUpload(id: string, filename: string) {
35
+ this.uploads = [...this.uploads, {
36
+ id,
37
+ filename,
38
+ pending: true,
39
+ percent: 0,
40
+ complete: false,
41
+ error: null,
42
+ }];
43
+ }
44
+
45
+ @Method()
46
+ async startUpload(id: string) {
47
+ this.uploads = this.uploads.map(u =>
48
+ u.id === id ? { ...u, pending: false } : u
49
+ );
50
+ }
51
+
52
+ @Method()
53
+ async updateProgress(id: string, percent: number) {
54
+ this.uploads = this.uploads.map(u =>
55
+ u.id === id ? { ...u, percent } : u
56
+ );
57
+ }
58
+
59
+ @Method()
60
+ async setError(id: string, error: string) {
61
+ this.uploads = this.uploads.map(u =>
62
+ u.id === id ? { ...u, error } : u
63
+ );
64
+ }
65
+
66
+ @Method()
67
+ async completeUpload(id: string) {
68
+ this.uploads = this.uploads.map(u =>
69
+ u.id === id ? { ...u, complete: true } : u
70
+ );
71
+ }
72
+
73
+ @Method()
74
+ async removeUpload(id: string) {
75
+ this.uploads = this.uploads.filter(u => u.id !== id);
76
+ }
77
+
78
+ render() {
79
+ return (
80
+ <Host>
81
+ <dialog ref={el => this.dialog = el}>
82
+ <div class="direct-upload-wrapper">
83
+ <div class="direct-upload-content">
84
+ <h3>Uploading your media</h3>
85
+ {this.uploads.map(upload => (
86
+ <progress-bar
87
+ key={upload.id}
88
+ class={{
89
+ "direct-upload--pending": upload.pending,
90
+ "direct-upload--complete": upload.complete,
91
+ "direct-upload--error": !!upload.error,
92
+ }}
93
+ percent={upload.percent}
94
+ title={upload.error || undefined}
95
+ >
96
+ {upload.filename}
97
+ </progress-bar>
98
+ ))}
99
+ </div>
100
+ </div>
101
+ </dialog>
102
+ </Host>
103
+ );
104
+ }
105
+ }
@@ -63,6 +63,16 @@ export namespace Components {
63
63
  */
64
64
  "uploadDialog": boolean;
65
65
  }
66
+ interface UploadDialog {
67
+ "addUpload": (id: string, filename: string) => Promise<void>;
68
+ "close": () => Promise<void>;
69
+ "completeUpload": (id: string) => Promise<void>;
70
+ "open": () => Promise<void>;
71
+ "removeUpload": (id: string) => Promise<void>;
72
+ "setError": (id: string, error: string) => Promise<void>;
73
+ "startUpload": (id: string) => Promise<void>;
74
+ "updateProgress": (id: string, percent: number) => Promise<void>;
75
+ }
66
76
  }
67
77
  export interface AttachmentFileCustomEvent<T> extends CustomEvent<T> {
68
78
  detail: T;
@@ -100,10 +110,17 @@ declare global {
100
110
  prototype: HTMLInputAttachmentElement;
101
111
  new (): HTMLInputAttachmentElement;
102
112
  };
113
+ interface HTMLUploadDialogElement extends Components.UploadDialog, HTMLStencilElement {
114
+ }
115
+ var HTMLUploadDialogElement: {
116
+ prototype: HTMLUploadDialogElement;
117
+ new (): HTMLUploadDialogElement;
118
+ };
103
119
  interface HTMLElementTagNameMap {
104
120
  "attachment-file": HTMLAttachmentFileElement;
105
121
  "attachment-preview": HTMLAttachmentPreviewElement;
106
122
  "input-attachment": HTMLInputAttachmentElement;
123
+ "upload-dialog": HTMLUploadDialogElement;
107
124
  }
108
125
  }
109
126
  declare namespace LocalJSX {
@@ -167,10 +184,13 @@ declare namespace LocalJSX {
167
184
  */
168
185
  "uploadDialog"?: boolean;
169
186
  }
187
+ interface UploadDialog {
188
+ }
170
189
  interface IntrinsicElements {
171
190
  "attachment-file": AttachmentFile;
172
191
  "attachment-preview": AttachmentPreview;
173
192
  "input-attachment": InputAttachment;
193
+ "upload-dialog": UploadDialog;
174
194
  }
175
195
  }
176
196
  export { LocalJSX as JSX };
@@ -180,6 +200,7 @@ declare module "@stencil/core" {
180
200
  "attachment-file": LocalJSX.AttachmentFile & JSXBase.HTMLAttributes<HTMLAttachmentFileElement>;
181
201
  "attachment-preview": LocalJSX.AttachmentPreview & JSXBase.HTMLAttributes<HTMLAttachmentPreviewElement>;
182
202
  "input-attachment": LocalJSX.InputAttachment & JSXBase.HTMLAttributes<HTMLInputAttachmentElement>;
203
+ "upload-dialog": LocalJSX.UploadDialog & JSXBase.HTMLAttributes<HTMLUploadDialogElement>;
183
204
  }
184
205
  }
185
206
  }
@@ -30,7 +30,8 @@ export const config: Config = {
30
30
  useESModules: true,
31
31
  moduleNameMapper: {
32
32
  "^@botandrose/progress-bar$": "<rootDir>/test-mocks/progress-bar.cjs",
33
- "^@botandrose/file-drop$": "<rootDir>/test-mocks/file-drop.cjs"
33
+ "^@botandrose/file-drop$": "<rootDir>/test-mocks/file-drop.cjs",
34
+ "\\.\\./upload-dialog/upload-dialog$": "<rootDir>/test-mocks/upload-dialog.cjs"
34
35
  },
35
36
  transformIgnorePatterns: [
36
37
  "node_modules/(?!(rails-request-json|@botandrose/progress-bar|@botandrose/file-drop|@rails/request\.js))"
@@ -0,0 +1,15 @@
1
+ // Mock for upload-dialog in Jest tests (CommonJS)
2
+ module.exports = {};
3
+ if (typeof customElements !== "undefined" && !customElements.get("upload-dialog")) {
4
+ customElements.define("upload-dialog", class extends HTMLElement {
5
+ connectedCallback() {}
6
+ open() {}
7
+ close() {}
8
+ addUpload() {}
9
+ startUpload() {}
10
+ updateProgress() {}
11
+ setError() {}
12
+ completeUpload() {}
13
+ removeUpload() {}
14
+ });
15
+ }
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Bard
4
4
  module AttachmentField
5
- VERSION = "0.4.1"
5
+ VERSION = "0.4.2"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bard-attachment_field
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Micah Geisel
@@ -368,6 +368,9 @@ files:
368
368
  - input-attachment/src/components/input-attachment/input-attachment.spec.tsx
369
369
  - input-attachment/src/components/input-attachment/input-attachment.tsx
370
370
  - input-attachment/src/components/input-attachment/readme.md
371
+ - input-attachment/src/components/upload-dialog/readme.md
372
+ - input-attachment/src/components/upload-dialog/upload-dialog.css
373
+ - input-attachment/src/components/upload-dialog/upload-dialog.tsx
371
374
  - input-attachment/src/global.d.ts
372
375
  - input-attachment/src/images/example.jpg
373
376
  - input-attachment/src/index.html
@@ -377,6 +380,7 @@ files:
377
380
  - input-attachment/stencil.config.ts
378
381
  - input-attachment/test-mocks/file-drop.cjs
379
382
  - input-attachment/test-mocks/progress-bar.cjs
383
+ - input-attachment/test-mocks/upload-dialog.cjs
380
384
  - input-attachment/tsconfig.json
381
385
  - lib/bard-attachment_field.rb
382
386
  - lib/bard/attachment_field.rb