@optionfactory/ful 4.0.11 → 4.0.12

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.
package/dist/ful.mjs CHANGED
@@ -1683,6 +1683,246 @@ class InputInstant extends Input {
1683
1683
  }
1684
1684
  }
1685
1685
 
1686
+ class InputFile extends Input {
1687
+ static l10n = {
1688
+ en: {
1689
+ 'dropzonelabel': 'Click or drop your files here',
1690
+ 'unaccepptablefiletype': "Only files of type {0} are supported",
1691
+ 'maxfilesizeexceeded': "Maximum supported file size is {0}",
1692
+ 'maxtotalsizeexceeded': "Maximum supported total file size is {0}"
1693
+ },
1694
+ it: {
1695
+ 'dropzonelabel': 'Clicca o trascina i file qui',
1696
+ 'unaccepptablefiletype': "Solo i file di tipo {0} sono supportati",
1697
+ 'maxfilesizeexceeded': "La dimensione massima di un file è di {0}",
1698
+ 'maxtotalsizeexceeded': "La dimensione massima complessiva dei file è di {0}"
1699
+ }
1700
+ }
1701
+ static observed = ['value', 'readonly:presence', "accept:csv", 'multiple:presence', "itemlist:presence", "dropzone:presence", "maxfilesize:number", "maxtotalsize:number"];
1702
+ #accept;
1703
+ #items;
1704
+ #dropzone;
1705
+ #warnings;
1706
+ _type() {
1707
+ return 'file';
1708
+ }
1709
+ static template = `
1710
+ <div class="form-label">
1711
+ <label>{{{{ slots.default }}}}</label>
1712
+ {{{{ slots.info }}}}
1713
+ </div>
1714
+ <div class="input-group">
1715
+ <span data-tpl-if="slots.ibefore" class="input-group-text">{{{{ slots.ibefore }}}}</span>
1716
+ {{{{ slots.before }}}}
1717
+ <input class="form-control" data-tpl-type="type" placeholder=" " form="">
1718
+ {{{{ slots.after }}}}
1719
+ <span data-tpl-if="slots.iafter" class="input-group-text">{{{{ slots.iafter }}}}</span>
1720
+ </div>
1721
+ <div data-ref="dropzone" class="dropzone" data-tpl-if="slots.dropzone">
1722
+ {{{{ slots.dropzone }}}}
1723
+ </div>
1724
+ <div data-ref="dropzone" class="dropzone" data-tpl-if="!slots.dropzone">
1725
+ {{ #l10n:t('dropzonelabel') }}
1726
+ </div>
1727
+ <div data-ref="items" class="items"></div>
1728
+ <ful-field-warnings></ful-field-warnings>
1729
+ <ful-field-error></ful-field-error>
1730
+ `;
1731
+ static templates = {
1732
+ items: `
1733
+ <div class="item" data-tpl-each="files" data-tpl-var="file" data-tpl-data-name="file.name">
1734
+ <div class="filename"><span>{{ file.name }}</span></div>
1735
+ <div class="size">{{ #bytes:format(file.size) }}</div>
1736
+ <button class="btn btn-sm btn-outline-danger"><i class="bi bi-x-lg"></i></button>
1737
+ </div>
1738
+ `,
1739
+ warning: `<ful-field-warning>{{ #l10n:t(key, args) }}</ful-field-warning>`
1740
+ }
1741
+ render(conf) {
1742
+ const { observed } = conf;
1743
+ super.render(conf);
1744
+ this.#items = this.querySelector("[data-ref=items]");
1745
+ this.#dropzone = this.querySelector("[data-ref=dropzone]");
1746
+ this.#warnings = this.querySelector("ful-field-warnings");
1747
+ this.accept = observed.accept;
1748
+ this.multiple = observed.multiple;
1749
+ this.itemlist = observed.itemlist;
1750
+ this.dropzone = observed.dropzone;
1751
+ this.maxfilesize = observed.maxfilesize;
1752
+ this.maxtotalsize = observed.maxtotalsize;
1753
+ this.#warnings.addEventListener('animationend', e => {
1754
+ e.target.remove();
1755
+ });
1756
+ this.#items.addEventListener('click', (e) => {
1757
+ if (!e.target.closest("button")) {
1758
+ return;
1759
+ }
1760
+ const fileName = e.target.closest(".item").dataset.name;
1761
+ const dt = new DataTransfer();
1762
+ [...this.files].filter(f => f.name !== fileName).forEach(f => dt.items.add(f));
1763
+ this.files = dt.files;
1764
+ this.#update();
1765
+ });
1766
+ this.#dropzone.addEventListener("click", (e) => {
1767
+ this.querySelector('input')?.click();
1768
+ });
1769
+
1770
+ this.#dropzone.addEventListener("dragover", (e) => {
1771
+ e.preventDefault();
1772
+ });
1773
+ this.#dropzone.addEventListener("drop", (e) => {
1774
+ e.preventDefault();
1775
+ const dt = new DataTransfer();
1776
+ [...e.dataTransfer.items].filter(i => i.kind === 'file').forEach(i => dt.items.add(i.getAsFile()));
1777
+ this.files = dt.files;
1778
+ this.#update();
1779
+ });
1780
+ this._input.addEventListener("change", (e) => {
1781
+ this.#update();
1782
+ });
1783
+ }
1784
+ #formatByteSize(v) {
1785
+ return (v > 1024 * 1024) ? `${Math.round(v / 1024 / 1024 * 100) / 100}MiB` : (v > 1024 ? `${Math.round(v / 1024 * 100) / 100}KiB` : `${v}B`);
1786
+ }
1787
+ #update() {
1788
+ this.setCustomValidity();
1789
+ this.#ensureAcceptable();
1790
+ this.#ensureFileSizes();
1791
+ this.#ensureTotalSize();
1792
+ if(this.#useItemlist){
1793
+ this.template('items').withOverlay({ files: this.files }).withModule('bytes', { format: this.#formatByteSize }).renderTo(this.#items);
1794
+ }else {
1795
+ this.#items.replaceChildren();
1796
+ }
1797
+ }
1798
+ warning(key, args) {
1799
+ this.template('warning').withOverlay({ key, args }).renderTo(this.#warnings);
1800
+ }
1801
+ #ensureAcceptable() {
1802
+ if (!this.#accept) {
1803
+ return;
1804
+ }
1805
+ const unacceptable = [...this.files]
1806
+ .filter(file => this.#accept.some(type => !file.name.toLowerCase().endsWith(type.toLowerCase())));
1807
+
1808
+ if (unacceptable.length === 0) {
1809
+ return;
1810
+ }
1811
+ this.warning('unaccepptablefiletype', this.#accept.join(","));
1812
+ const dt = new DataTransfer();
1813
+ [...this.files].filter(f => !unacceptable.includes(f)).forEach(f => dt.items.add(f));
1814
+ this.files = dt.files;
1815
+ }
1816
+ #ensureFileSizes() {
1817
+ if (this.#maxfilesize === null) {
1818
+ return;
1819
+ }
1820
+ const oversized = [...this.files]
1821
+ .filter(file => file.size > this.#maxfilesize);
1822
+ if (oversized.length === 0) {
1823
+ return;
1824
+ }
1825
+ this.warning('maxfilesizeexceeded', this.#formatByteSize(this.#maxfilesize));
1826
+ const dt = new DataTransfer();
1827
+ [...this.files].filter(f => !oversized.includes(f)).forEach(f => dt.items.add(f));
1828
+ this.files = dt.files;
1829
+ }
1830
+ #ensureTotalSize() {
1831
+ if (this.#maxtotalsize === null) {
1832
+ return;
1833
+ }
1834
+ const totalSize = [...this.files].reduce((acc, file) => acc + file.size, 0);
1835
+ if (totalSize <= this.#maxtotalsize) {
1836
+ return;
1837
+ }
1838
+ this.warning('maxtotalsizeexceeded', this.#formatByteSize(this.#maxtotalsize));
1839
+ this.files = new DataTransfer().files;
1840
+ }
1841
+ get accept() {
1842
+ return this.#accept;
1843
+ }
1844
+ set accept(vs) {
1845
+ this._input.accept = vs.join(",");
1846
+ this.#accept = vs;
1847
+ this.reflect(() => {
1848
+ this.setAttribute('accept', this._input.accept);
1849
+ });
1850
+ }
1851
+ get multiple() {
1852
+ return this._input.multiple;
1853
+ }
1854
+ set multiple(v) {
1855
+ this._input.multiple = v;
1856
+ this.reflect(() => {
1857
+ this.setAttribute('multiple', this._input.multiple);
1858
+ });
1859
+ }
1860
+ get files() {
1861
+ return this._input.files;
1862
+ }
1863
+ set files(vs) {
1864
+ this._input.files = vs;
1865
+ }
1866
+ get file() {
1867
+ return this.files[0] ?? null;
1868
+ }
1869
+ set file(v) {
1870
+ const dt = new DataTransfer();
1871
+ dt.items.add(v);
1872
+ this.files = dt.files;
1873
+ }
1874
+ get value() {
1875
+ const names = Array.from(this._input.files).map(f => f.name);
1876
+ return this.multiple ? names : (names[0] ?? null);
1877
+ }
1878
+ set value(v) {
1879
+ //TODO:
1880
+ }
1881
+ #maxfilesize;
1882
+ get maxfilesize() {
1883
+ return this.#maxfilesize;
1884
+ }
1885
+ set maxfilesize(v) {
1886
+ this.#maxfilesize = v;
1887
+ this.reflect(() => {
1888
+ this.setAttribute('maxfilesize', v);
1889
+ });
1890
+
1891
+ }
1892
+ #maxtotalsize;
1893
+ get maxtotalsize() {
1894
+ return this.#maxtotalsize;
1895
+ }
1896
+ set maxtotalsize(v) {
1897
+ this.#maxtotalsize = v;
1898
+ this.reflect(() => {
1899
+ this.setAttribute('maxtotalsize', v);
1900
+ });
1901
+ }
1902
+ #useItemlist;
1903
+ get itemlist() {
1904
+ return this.#useItemlist;
1905
+ }
1906
+ set itemlist(v) {
1907
+ this.#useItemlist = v;
1908
+ Attributes.toggle(this.#items, "hidden", !v);
1909
+ this.reflect(() => {
1910
+ Attributes.toggle(this, "itemlist", v);
1911
+ });
1912
+ }
1913
+ #useDropzone;
1914
+ get dropzone() {
1915
+ return this.#useDropzone;
1916
+ }
1917
+ set dropzone(v) {
1918
+ this.#useDropzone = v;
1919
+ Attributes.toggle(this.#dropzone, "hidden", !v);
1920
+ this.reflect(() => {
1921
+ Attributes.toggle(this, "dropzone", v);
1922
+ });
1923
+ }
1924
+ }
1925
+
1686
1926
  class RemoteLoader {
1687
1927
  #http;
1688
1928
  #url;
@@ -2087,7 +2327,7 @@ class Select extends ParsedElement {
2087
2327
  b.innerText = v[0];
2088
2328
  return b;
2089
2329
  });
2090
- this.#badges.innerHTML = '';
2330
+ this.#badges.replaceChildren();
2091
2331
  this.#badges.append(...badges);
2092
2332
  }
2093
2333
  set value(vs) {
@@ -2726,7 +2966,7 @@ class Table extends ParsedElement {
2726
2966
  return await this.load(this.#latestRequest.pageRequest, this.#latestRequest.sortRequest, this.#latestRequest.filterRequest);
2727
2967
  }
2728
2968
  async load(pageRequest, sortRequest, filterRequest) {
2729
- this.#body.innerHTML = "";
2969
+ this.#body.replaceChildren();
2730
2970
  this.#loading.removeAttribute("hidden", "");
2731
2971
  this.#feedback.setAttribute("hidden", "");
2732
2972
  this.#noAutoload.setAttribute("hidden", "");
@@ -3045,6 +3285,23 @@ class TextFilter extends ParsedElement {
3045
3285
  }
3046
3286
  }
3047
3287
 
3288
+ class LocalizationModule {
3289
+ static t(k, ...args) {
3290
+ //@ts-ignore
3291
+ const format = this.l10n[this.language][k] ?? this.l10n['en'][k] ?? k;
3292
+ if (args.length === 0) {
3293
+ return format;
3294
+ }
3295
+ return format.replace(/{(\d+)}/g, (m, is) => {
3296
+ return args[Number(is)];
3297
+ });
3298
+ }
3299
+ static tl(k, args) {
3300
+ return LocalizationModule.t(k, ...args);
3301
+ }
3302
+
3303
+ }
3304
+
3048
3305
  class Plugin {
3049
3306
  configure(registry) {
3050
3307
  const httpClient = HttpClient.builder()
@@ -3052,22 +3309,13 @@ class Plugin {
3052
3309
  .withRedirectOnUnauthorized("/")
3053
3310
  .build();
3054
3311
  registry
3055
- .defineModule("l10n", {
3056
- t: function (k, ...args) {
3057
- const format = this.l10n[this.language][k] ?? this.l10n['en'][k] ?? k;
3058
- if (args.length === 0) {
3059
- return format;
3060
- }
3061
- return format.replace(/{(\d+)}/g, (m, is) => {
3062
- return args[Number(is)];
3063
- });
3064
- }
3065
- })
3312
+ .defineModule("l10n", LocalizationModule)
3066
3313
  .defineComponent('http-client', httpClient)
3067
3314
  .defineElement('ful-spinner', Spinner)
3068
3315
  .defineElement('ful-form', Form)
3069
3316
  .defineElement('ful-checkbox', Checkbox)
3070
3317
  .defineElement('ful-input', Input)
3318
+ .defineElement('ful-input-file', InputFile)
3071
3319
  .defineElement('ful-local-date', LocalDate)
3072
3320
  .defineElement('ful-instant', Instant)
3073
3321
  .defineElement('ful-input-local-date', InputLocalDate)
@@ -3091,5 +3339,5 @@ class Plugin {
3091
3339
  }
3092
3340
  }
3093
3341
 
3094
- export { AsyncEvents, AuthorizationCodeFlow, AuthorizationCodeFlowInterceptor, AuthorizationCodeFlowSession, Base64, Bindings, Checkbox, Dropdown, Failure, Form, FormLoader, Hex, HttpClient, HttpClientError, Input, InputInstant, InputLocalDate, InputLocalTime, Instant, InstantFilter, Loaders, LocalDate, LocalDateFilter, LocalStorage, MediaType, Pagination, Plugin, RadioGroup, Select, SelectLoader, SessionStorage, SortButton, Spinner, Table, TableSchemaParser, TextFilter, Timing, VersionedLocalStorage, VersionedSessionStorage };
3342
+ export { AsyncEvents, AuthorizationCodeFlow, AuthorizationCodeFlowInterceptor, AuthorizationCodeFlowSession, Base64, Bindings, Checkbox, Dropdown, Failure, Form, FormLoader, Hex, HttpClient, HttpClientError, Input, InputFile, InputInstant, InputLocalDate, InputLocalTime, Instant, InstantFilter, Loaders, LocalDate, LocalDateFilter, LocalStorage, LocalizationModule, MediaType, Pagination, Plugin, RadioGroup, Select, SelectLoader, SessionStorage, SortButton, Spinner, Table, TableSchemaParser, TextFilter, Timing, VersionedLocalStorage, VersionedSessionStorage };
3095
3343
  //# sourceMappingURL=ful.mjs.map