@optionfactory/ful 4.0.13 → 4.0.15

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
@@ -1461,7 +1461,7 @@ class Input extends ParsedElement {
1461
1461
  _fragment(type, slots) {
1462
1462
  return this.template().withOverlay({ type, slots }).render();
1463
1463
  }
1464
- render({ slots, observed, disabled }) {
1464
+ render({ slots, observed, disabled, skipValueSetup }) {
1465
1465
  const type = this._type();
1466
1466
  const fragment = this._fragment(type, slots);
1467
1467
  this._input = fragment.querySelector("input,textarea");
@@ -1469,7 +1469,9 @@ class Input extends ParsedElement {
1469
1469
  Attributes.forward('input-', this, this._input);
1470
1470
  this.disabled = disabled;
1471
1471
  this.readonly = observed.readonly;
1472
- this.value = observed.value;
1472
+ if(!skipValueSetup){
1473
+ this.value = observed.value;
1474
+ }
1473
1475
 
1474
1476
  this._input.addEventListener('change', (evt) => {
1475
1477
  evt.stopPropagation();
@@ -1724,24 +1726,24 @@ class InputFile extends Input {
1724
1726
  <div data-ref="dropzone" class="dropzone" data-tpl-if="!slots.dropzone">
1725
1727
  {{ #l10n:t('dropzonelabel') }}
1726
1728
  </div>
1727
- <div data-ref="items" class="items"></div>
1729
+ <ful-item-list></ful-item-list>
1728
1730
  <ful-field-warnings></ful-field-warnings>
1729
1731
  <ful-field-error></ful-field-error>
1730
1732
  `;
1731
1733
  static templates = {
1732
1734
  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>
1735
+ <ful-item data-tpl-each="files" data-tpl-var="file" data-tpl-data-name="file.name">
1736
+ <div>{{ file.name }}</div>
1737
+ <div>{{ #bytes:format(file.size) }}</div>
1738
+ <button type="button" class="btn btn-sm btn-outline-danger bi bi-x-lg"></button>
1739
+ </ful-item>
1738
1740
  `,
1739
1741
  warning: `<ful-field-warning>{{ #l10n:t(key, args) }}</ful-field-warning>`
1740
1742
  }
1741
1743
  render(conf) {
1742
1744
  const { observed } = conf;
1743
1745
  super.render(conf);
1744
- this.#items = this.querySelector("[data-ref=items]");
1746
+ this.#items = this.querySelector("ful-item-list");
1745
1747
  this.#dropzone = this.querySelector("[data-ref=dropzone]");
1746
1748
  this.#warnings = this.querySelector("ful-field-warnings");
1747
1749
  this.accept = observed.accept;
@@ -1757,7 +1759,7 @@ class InputFile extends Input {
1757
1759
  if (!e.target.closest("button")) {
1758
1760
  return;
1759
1761
  }
1760
- const fileName = e.target.closest(".item").dataset.name;
1762
+ const fileName = e.target.closest("ful-item").dataset.name;
1761
1763
  const dt = new DataTransfer();
1762
1764
  [...this.files].filter(f => f.name !== fileName).forEach(f => dt.items.add(f));
1763
1765
  this.files = dt.files;
@@ -1789,11 +1791,7 @@ class InputFile extends Input {
1789
1791
  this.#ensureAcceptable();
1790
1792
  this.#ensureFileSizes();
1791
1793
  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
- }
1794
+ this.template('items').withOverlay({ files: this.files }).withModule('bytes', { format: this.#formatByteSize }).renderTo(this.#items);
1797
1795
  }
1798
1796
  warning(key, args) {
1799
1797
  this.template('warning').withOverlay({ key, args }).renderTo(this.#warnings);
@@ -1854,7 +1852,7 @@ class InputFile extends Input {
1854
1852
  set multiple(v) {
1855
1853
  this._input.multiple = v;
1856
1854
  this.reflect(() => {
1857
- this.setAttribute('multiple', this._input.multiple);
1855
+ Attributes.toggle(this, "multiple", v);
1858
1856
  });
1859
1857
  }
1860
1858
  get files() {
@@ -1887,7 +1885,6 @@ class InputFile extends Input {
1887
1885
  this.reflect(() => {
1888
1886
  this.setAttribute('maxfilesize', v);
1889
1887
  });
1890
-
1891
1888
  }
1892
1889
  #maxtotalsize;
1893
1890
  get maxtotalsize() {
@@ -1905,7 +1902,6 @@ class InputFile extends Input {
1905
1902
  }
1906
1903
  set itemlist(v) {
1907
1904
  this.#useItemlist = v;
1908
- Attributes.toggle(this.#items, "hidden", !v);
1909
1905
  this.reflect(() => {
1910
1906
  Attributes.toggle(this, "itemlist", v);
1911
1907
  });
@@ -1916,7 +1912,6 @@ class InputFile extends Input {
1916
1912
  }
1917
1913
  set dropzone(v) {
1918
1914
  this.#useDropzone = v;
1919
- Attributes.toggle(this.#dropzone, "hidden", !v);
1920
1915
  this.reflect(() => {
1921
1916
  Attributes.toggle(this, "dropzone", v);
1922
1917
  });
@@ -2145,7 +2140,7 @@ class Dropdown extends ParsedElement {
2145
2140
  }
2146
2141
 
2147
2142
  class Select extends ParsedElement {
2148
- static observed = ['value:csvm', 'readonly:presence']
2143
+ static observed = ['value:csvm', 'readonly:presence', 'itemlist:presence']
2149
2144
  static slots = true
2150
2145
  static template = `
2151
2146
  <div class="form-label">
@@ -2165,8 +2160,17 @@ class Select extends ParsedElement {
2165
2160
  {{{{ slots.after }}}}
2166
2161
  <span data-tpl-if="slots.iafter" class="input-group-text">{{{{ slots.iafter }}}}</span>
2167
2162
  </div>
2163
+ <ful-item-list></ful-item-list>
2168
2164
  <ful-field-error></ful-field-error>
2169
2165
  `;
2166
+ static templates = {
2167
+ items: `
2168
+ <ful-item data-tpl-each="entries" data-tpl-var="entry" data-tpl-data-key="entry[0]">
2169
+ <div>{{ entry[1][0] }}</div>
2170
+ <button type="button" class="btn btn-sm btn-outline-danger bi bi-x-lg"></button>
2171
+ </ful-item>
2172
+ `
2173
+ }
2170
2174
  static mappers = {
2171
2175
  "csvm": (v, name, el) => {
2172
2176
  if (el.hasAttribute("multiple")) {
@@ -2181,6 +2185,7 @@ class Select extends ParsedElement {
2181
2185
  #badges
2182
2186
  #ddmenu
2183
2187
  #input
2188
+ #items;
2184
2189
  #multiple
2185
2190
  #fieldError
2186
2191
  #values = new Map()
@@ -2196,12 +2201,14 @@ class Select extends ParsedElement {
2196
2201
  await this.#loader.prefetch?.();
2197
2202
  const fragment = this.template().withOverlay({ slots, name }).render();
2198
2203
  this.#input = fragment.querySelector('input');
2204
+ this.#items = fragment.querySelector("ful-item-list");
2199
2205
  Attributes.forward('input-', this, this.#input);
2200
2206
  this.#badges = fragment.querySelector('badges');
2201
2207
 
2202
2208
  this.value = observed.value;
2203
2209
  this.disabled = disabled;
2204
2210
  this.readonly = observed.readonly;
2211
+ this.itemlist = observed.itemlist;
2205
2212
 
2206
2213
  this.#ddmenu = fragment.querySelector('ful-dropdown');
2207
2214
  const label = fragment.querySelector('label');
@@ -2226,6 +2233,22 @@ class Select extends ParsedElement {
2226
2233
  this.#input.focus();
2227
2234
  dload();
2228
2235
  });
2236
+ this.#items.addEventListener('click', (e) => {
2237
+ e.stopPropagation();
2238
+ if (!e.target.closest("button")) {
2239
+ return;
2240
+ }
2241
+ if(this.disabled || this.readonly){
2242
+ return;
2243
+ }
2244
+ const idx = [...this.#items.children].indexOf(e.target.closest('ful-item'));
2245
+ if (idx === -1) {
2246
+ return;
2247
+ }
2248
+ this.#values.delete(Array.from(this.#values.keys()).pop());
2249
+ this.#changed();
2250
+ this.#syncBadges();
2251
+ });
2229
2252
  this.#badges.addEventListener('click', (e) => {
2230
2253
  e.stopPropagation();
2231
2254
  if(this.disabled || this.readonly){
@@ -2333,6 +2356,8 @@ class Select extends ParsedElement {
2333
2356
  });
2334
2357
  this.#badges.replaceChildren();
2335
2358
  this.#badges.append(...badges);
2359
+ this.#items.replaceChildren();
2360
+ this.template('items').withOverlay({ entries: this.#values.entries() }).renderTo(this.#items);
2336
2361
  }
2337
2362
  set value(vs) {
2338
2363
  if(vs === null){
@@ -2374,6 +2399,16 @@ class Select extends ParsedElement {
2374
2399
  Attributes.toggle(this, 'readonly', v);
2375
2400
  });
2376
2401
  }
2402
+ #useItemlist;
2403
+ get itemlist() {
2404
+ return this.#useItemlist;
2405
+ }
2406
+ set itemlist(v) {
2407
+ this.#useItemlist = v;
2408
+ this.reflect(() => {
2409
+ Attributes.toggle(this, "itemlist", v);
2410
+ });
2411
+ }
2377
2412
  focus(options) {
2378
2413
  this.#input.focus(options);
2379
2414
  }
@@ -3011,15 +3046,16 @@ class Table extends ParsedElement {
3011
3046
  }
3012
3047
  }
3013
3048
 
3014
- class InstantFilter extends ParsedElement {
3015
- static observed = ["value:json"];
3016
- static slots = true;
3049
+ class InstantFilter extends Input {
3050
+ static observed = ['value:json', 'readonly:presence'];
3017
3051
  static template = `
3018
- <div class="form-label" data-tpl-if="label">
3019
- <label>{{{{ label }}}}</label>
3052
+ <div class="form-label">
3053
+ <label>{{{{ slots.default }}}}</label>
3020
3054
  {{{{ slots.info }}}}
3021
3055
  </div>
3022
3056
  <div class="input-group">
3057
+ <span data-tpl-if="slots.ibefore" class="input-group-text">{{{{ slots.ibefore }}}}</span>
3058
+ {{{{ slots.before }}}}
3023
3059
  <button data-ref="operator" class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" value="LTE" form="">&PrecedesSlantEqual;</button>
3024
3060
  <ul class="dropdown-menu">
3025
3061
  <li><a class="dropdown-item" role="button" value="EQ">=</a></li>
@@ -3032,32 +3068,21 @@ class InstantFilter extends ParsedElement {
3032
3068
  </ul>
3033
3069
  <input data-ref="value1" type="datetime-local" class="form-control" form="">
3034
3070
  <input data-ref="value2" type="datetime-local" class="form-control" form="" hidden>
3035
- <span class="input-group-text"><i class="bi bi-search"></i></span>
3071
+ {{{{ slots.after }}}}
3072
+ <span data-tpl-if="slots.iafter" class="input-group-text">{{{{ slots.iafter }}}}</span>
3036
3073
  </div>
3037
3074
  <ful-field-error></ful-field-error>
3038
3075
  `;
3039
- static formAssociated = true;
3040
3076
  #operator;
3041
3077
  #value1;
3042
3078
  #value2;
3043
- #fieldError;
3044
- constructor() {
3045
- super();
3046
- this.internals = this.attachInternals();
3047
- }
3048
- render({ slots }) {
3049
- const label = Fragments.toHtml(slots.default.cloneNode(true)).trim().length === 0 ? null : slots.default;
3050
- const name = this.getAttribute("name");
3051
- const fragment = this.template().withOverlay({ slots, label, name }).render();
3052
- this.#operator = fragment.querySelector('[data-ref=operator]');
3053
- this.#value1 = fragment.querySelector('[data-ref=value1]');
3054
- this.#value2 = fragment.querySelector('[data-ref=value2]');
3055
- this.#fieldError = fragment.querySelector('ful-field-error');
3056
- const labelEl = fragment.querySelector('label');
3057
- labelEl?.addEventListener('click', () => this.focus());
3058
- this.#value1.ariaDescribedByElements = [this.#fieldError];
3059
- this.#value1.ariaLabelledByElements = labelEl ? [labelEl] : [];
3060
- this.replaceChildren(fragment);
3079
+ render(conf) {
3080
+ super.render({...conf, skipValueSetup: true});
3081
+ this.#operator = this.querySelector('[data-ref=operator]');
3082
+ this.#value1 = this.querySelector('[data-ref=value1]');
3083
+ this.#value2 = this.querySelector('[data-ref=value2]');
3084
+ this.value = conf.observed.value;
3085
+
3061
3086
  this.addEventListener('click', (evt) => {
3062
3087
  const target = /** @type HTMLElement */ (evt.target);
3063
3088
  if (!target.matches('ul > li > a')) {
@@ -3080,42 +3105,25 @@ class InstantFilter extends ParsedElement {
3080
3105
  if (v === null || v === undefined) {
3081
3106
  this.#value1.value = '';
3082
3107
  this.#value2.value = '';
3083
- this.reflect(() => {
3084
- this.removeAttribute('value');
3085
- });
3086
3108
  return;
3087
3109
  }
3088
3110
  const [operator, ...values] = v;
3089
3111
  this.#operator.setAttribute('value', operator);
3090
3112
  this.#value1.value = values[0] ? Instant.isoToLocal(values[0]) : values[0];
3091
3113
  this.#value2.value = values[1] ? Instant.isoToLocal(values[1]) : values[1];
3092
- this.reflect(() => {
3093
- this.setAttribute('value', JSON.stringify(v));
3094
- });
3095
- }
3096
- focus(options) {
3097
- this.#value1.focus(options);
3098
- }
3099
- setCustomValidity(error) {
3100
- if (!error) {
3101
- this.internals.setValidity({});
3102
- this.#fieldError.innerText = "";
3103
- return;
3104
- }
3105
- this.internals.setValidity({ customError: true }, " ");
3106
- this.#fieldError.innerText = error;
3107
3114
  }
3108
3115
  }
3109
3116
 
3110
- class LocalDateFilter extends ParsedElement {
3111
- static observed = ["value:json"];
3112
- static slots = true;
3117
+ class LocalDateFilter extends Input {
3118
+ static observed = ["value:json", 'readonly:presence'];
3113
3119
  static template = `
3114
- <div class="form-label" data-tpl-if="label">
3115
- <label>{{{{ label }}}}</label>
3120
+ <div class="form-label">
3121
+ <label>{{{{ slots.default }}}}</label>
3116
3122
  {{{{ slots.info }}}}
3117
3123
  </div>
3118
3124
  <div class="input-group">
3125
+ <span data-tpl-if="slots.ibefore" class="input-group-text">{{{{ slots.ibefore }}}}</span>
3126
+ {{{{ slots.before }}}}
3119
3127
  <button data-ref="operator" class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" value="EQ" form="">=</button>
3120
3128
  <ul class="dropdown-menu">
3121
3129
  <li><a class="dropdown-item" role="button" value="EQ">=</a></li>
@@ -3128,32 +3136,22 @@ class LocalDateFilter extends ParsedElement {
3128
3136
  </ul>
3129
3137
  <input data-ref="value1" type="date" class="form-control" form="">
3130
3138
  <input data-ref="value2" type="date" class="form-control" form="" hidden>
3131
- <span class="input-group-text"><i class="bi bi-search"></i></span>
3139
+ {{{{ slots.after }}}}
3140
+ <span data-tpl-if="slots.iafter" class="input-group-text">{{{{ slots.iafter }}}}</span>
3132
3141
  </div>
3133
3142
  <ful-field-error></ful-field-error>
3134
3143
  `;
3135
- static formAssociated = true;
3136
3144
  #operator;
3137
3145
  #value1;
3138
3146
  #value2;
3139
- #fieldError;
3140
- constructor() {
3141
- super();
3142
- this.internals = this.attachInternals();
3143
- }
3144
- render({ slots }) {
3145
- const label = Fragments.toHtml(slots.default.cloneNode(true)).trim().length === 0 ? null : slots.default;
3146
- const name = this.getAttribute("name");
3147
- const fragment = this.template().withOverlay({ slots, label, name }).render();
3148
- this.#operator = fragment.querySelector('[data-ref=operator]');
3149
- this.#value1 = fragment.querySelector('[data-ref=value1]');
3150
- this.#value2 = fragment.querySelector('[data-ref=value2]');
3151
- this.#fieldError = fragment.querySelector('ful-field-error');
3152
- const labelEl = fragment.querySelector('label');
3153
- labelEl?.addEventListener('click', () => this.focus());
3154
- this.#value1.ariaDescribedByElements = [this.#fieldError];
3155
- this.#value1.ariaLabelledByElements = labelEl ? [labelEl] : [];
3156
- this.replaceChildren(fragment);
3147
+ render(conf) {
3148
+ super.render({...conf, skipValueSetup: true});
3149
+
3150
+ this.#operator = this.querySelector('[data-ref=operator]');
3151
+ this.#value1 = this.querySelector('[data-ref=value1]');
3152
+ this.#value2 = this.querySelector('[data-ref=value2]');
3153
+ this.value = conf.observed.value;
3154
+
3157
3155
  this.addEventListener('click', (evt) => {
3158
3156
  const target = /** @type HTMLElement */(evt.target);
3159
3157
  if (!target.matches('ul > li > a')) {
@@ -3175,42 +3173,25 @@ class LocalDateFilter extends ParsedElement {
3175
3173
  if (v === null || v === undefined) {
3176
3174
  this.#value1.value = '';
3177
3175
  this.#value2.value = '';
3178
- this.reflect(() => {
3179
- this.removeAttribute('value');
3180
- });
3181
3176
  return;
3182
3177
  }
3183
3178
  const [operator, ...values] = v;
3184
3179
  this.#operator.setAttibute('value', operator);
3185
3180
  this.#value1.value = values[0];
3186
3181
  this.#value2.value = values[1];
3187
- this.reflect(() => {
3188
- this.setAttribute('value', JSON.stringify(v));
3189
- });
3190
- }
3191
- focus(options) {
3192
- this.#value1.focus(options);
3193
- }
3194
- setCustomValidity(error) {
3195
- if (!error) {
3196
- this.internals.setValidity({});
3197
- this.#fieldError.innerText = "";
3198
- return;
3199
- }
3200
- this.internals.setValidity({ customError: true }, " ");
3201
- this.#fieldError.innerText = error;
3202
3182
  }
3203
3183
  }
3204
3184
 
3205
- class TextFilter extends ParsedElement {
3206
- static observed = ["value:json"];
3207
- static slots = true;
3185
+ class TextFilter extends Input {
3186
+ static observed = ["value:json", 'readonly:presence'];
3208
3187
  static template = `
3209
- <div class="form-label" data-tpl-if="label">
3210
- <label>{{{{ label }}}}</label>
3188
+ <div class="form-label">
3189
+ <label>{{{{ slots.default }}}}</label>
3211
3190
  {{{{ slots.info }}}}
3212
3191
  </div>
3213
3192
  <div class="input-group">
3193
+ <span data-tpl-if="slots.ibefore" class="input-group-text">{{{{ slots.ibefore }}}}</span>
3194
+ {{{{ slots.before }}}}
3214
3195
  <button data-ref="operator" class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false" value="CONTAINS" form="">&mldr;a&mldr;</button>
3215
3196
  <ul class="dropdown-menu">
3216
3197
  <li><a class="dropdown-item" role="button" value="CONTAINS">&mldr;a&mldr;</a></li>
@@ -3219,30 +3200,20 @@ class TextFilter extends ParsedElement {
3219
3200
  <li><a class="dropdown-item" role="button" value="EQ">=</a></li>
3220
3201
  </ul>
3221
3202
  <input data-ref="value" type="text" class="form-control" form="">
3222
- <span class="input-group-text"><i class="bi bi-search"></i></span>
3203
+ {{{{ slots.after }}}}
3204
+ <span data-tpl-if="slots.iafter" class="input-group-text">{{{{ slots.iafter }}}}</span>
3223
3205
  </div>
3224
3206
  <ful-field-error></ful-field-error>
3225
3207
  `;
3226
- static formAssociated = true;
3227
3208
  #operator;
3228
3209
  #value;
3229
- #fieldError;
3230
- constructor() {
3231
- super();
3232
- this.internals = this.attachInternals();
3233
- }
3234
- render({ slots }) {
3235
- const label = Fragments.toHtml(slots.default.cloneNode(true)).trim().length === 0 ? null : slots.default;
3236
- const name = this.getAttribute("name");
3237
- const fragment = this.template().withOverlay({ slots, label, name }).render();
3238
- this.#operator = fragment.querySelector('[data-ref=operator]');
3239
- this.#value = fragment.querySelector('[data-ref=value]');
3240
- this.#fieldError = fragment.querySelector('ful-field-error');
3241
- const labelEl = fragment.querySelector('label');
3242
- labelEl?.addEventListener('click', () => this.focus());
3243
- this.#value.ariaDescribedByElements = [this.#fieldError];
3244
- this.#value.ariaLabelledByElements = labelEl ? [labelEl] : [];
3245
- this.replaceChildren(fragment);
3210
+ render(conf) {
3211
+ super.render({...conf, skipValueSetup: true});
3212
+
3213
+ this.#operator = this.querySelector('[data-ref=operator]');
3214
+ this.#value = this.querySelector('[data-ref=value]');
3215
+ this.value = conf.observed.value;
3216
+
3246
3217
  this.addEventListener('click', (evt) => {
3247
3218
  const target = /** @type HTMLElement */(evt.target);
3248
3219
  if (!target.matches('ul > li > a')) {
@@ -3263,29 +3234,11 @@ class TextFilter extends ParsedElement {
3263
3234
  set value(v) {
3264
3235
  if (v === null || v === undefined) {
3265
3236
  this.#value.value = '';
3266
- this.reflect(() => {
3267
- this.removeAttribute('value');
3268
- });
3269
3237
  return;
3270
3238
  }
3271
3239
  const [operator, sensitivity, value] = v;
3272
3240
  this.#operator.setAttribute('value', operator);
3273
3241
  this.#value.value = value;
3274
- this.reflect(() => {
3275
- this.setAttribute('value', JSON.stringify(v));
3276
- });
3277
- }
3278
- focus(options) {
3279
- this.#value.focus(options);
3280
- }
3281
- setCustomValidity(error) {
3282
- if (!error) {
3283
- this.internals.setValidity({});
3284
- this.#fieldError.innerText = "";
3285
- return;
3286
- }
3287
- this.internals.setValidity({ customError: true }, " ");
3288
- this.#fieldError.innerText = error;
3289
3242
  }
3290
3243
  }
3291
3244