@optionfactory/ful 0.42.0 → 0.44.0

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.iife.js CHANGED
@@ -560,16 +560,41 @@ var ful = (function (exports) {
560
560
  }
561
561
  };
562
562
 
563
+ // SyncEvent.on($0, 'asd', async e => { await ful.timing.sleep(10_000); return 3; })
564
+ // const [success, results] = await new SyncEvent("asd").dispatchTo($0);
565
+ class SyncEvent extends CustomEvent {
566
+ #results;
567
+ constructor(type, options) {
568
+ super(type, options);
569
+ this.#results = [];
570
+ }
571
+
572
+ async dispatchTo(el) {
573
+ // unlike "native" events, which are fired by the browser and invoke
574
+ // event handlers asynchronously via the event loop, dispatchEvent()
575
+ // invokes event handlers synchronously.
576
+ // see: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent
577
+ const success = el.dispatchEvent(this);
578
+ const results = await Promise.all(this.#results);
579
+ return [success, results];
580
+ }
581
+
582
+ static on(el, type, h, useCapture) {
583
+ el.addEventListener(type, e => {
584
+ //e *must* be an async event
585
+ e.#results.push(h(e));
586
+ }, useCapture);
587
+ }
588
+ }
589
+
563
590
  class Fragments {
564
591
  static fromHtml(...html) {
565
592
  const el = document.createElement('div');
566
593
  el.innerHTML = html.join("");
567
594
  const fragment = new DocumentFragment();
568
- Array.from(el.childNodes).forEach(node => {
569
- fragment.appendChild(node);
570
- });
595
+ fragment.append(...el.childNodes);
571
596
  return fragment;
572
- }
597
+ }
573
598
  static toHtml(fragment) {
574
599
  var r = document.createElement("root");
575
600
  r.appendChild(fragment);
@@ -577,17 +602,12 @@ var ful = (function (exports) {
577
602
  }
578
603
  static from(...nodes) {
579
604
  const fragment = new DocumentFragment();
580
- for (let i = 0; i !== nodes.length; ++i) {
581
- fragment.appendChild(nodes[i]);
582
- }
605
+ fragment.append(...nodes);
583
606
  return fragment;
584
607
  }
585
608
  static fromChildNodes(el) {
586
- const nodes = Array.from(el.childNodes);
587
609
  const fragment = new DocumentFragment();
588
- for (let i = 0; i !== nodes.length; ++i) {
589
- fragment.appendChild(nodes[i]);
590
- }
610
+ fragment.append(...el.childNodes);
591
611
  return fragment;
592
612
  }
593
613
  }
@@ -637,20 +657,85 @@ var ful = (function (exports) {
637
657
  }
638
658
  }
639
659
 
640
- const Templated = (SuperClass, template) => {
641
- return class extends SuperClass {
642
- #rendered;
643
- async connectedCallback() {
644
- if (this.#rendered) {
660
+ class Nodes {
661
+ static isParsed(el) {
662
+ for (var c = el; c; c = c.parentNode) {
663
+ if (c.nextSibling) {
664
+ return true;
665
+ }
666
+ }
667
+ return false;
668
+ }
669
+ }
670
+
671
+ class UpgradeQueue {
672
+ #q = [];
673
+ constructor() {
674
+ document.addEventListener('DOMContentLoaded', this.dequeue.bind(this));
675
+ }
676
+ enqueue(el) {
677
+ if (!this.#q.length) {
678
+ requestAnimationFrame(this.dequeue.bind(this));
679
+ }
680
+ this.#q.push(el);
681
+ }
682
+ dequeue() {
683
+ this.#q.splice(0).forEach(el => el.upgrade());
684
+ }
685
+ }
686
+
687
+ const upgradeQueue = new UpgradeQueue();
688
+
689
+ class ParsedElement extends HTMLElement {
690
+ #parsed;
691
+ connectedCallback() {
692
+ if (this.#parsed) {
693
+ return;
694
+ }
695
+ if (this.ownerDocument.readyState === 'complete' || Nodes.isParsed(this)) {
696
+ upgradeQueue.enqueue(this);
697
+ return;
698
+ }
699
+ this.ownerDocument.addEventListener('DOMContentLoaded', () => {
700
+ observer.disconnect();
701
+ upgradeQueue.enqueue(this);
702
+ });
703
+ const observer = new MutationObserver(() => {
704
+ if (!Nodes.isParsed(this)) {
645
705
  return;
646
706
  }
707
+ observer.disconnect();
708
+ upgradeQueue.enqueue(this);
709
+ });
710
+ observer.observe(this.parentNode, { childList: true, subtree: true });
711
+ }
712
+ attributeChangedCallback(name, oldValue, newValue) {
713
+ if (!this.#parsed || oldValue === newValue) {
714
+ return;
715
+ }
716
+ this[name] = newValue;
717
+ const method = this[`on${name.charAt(0).toUpperCase()}${name.substr(1).toLowerCase()}Changed`];
718
+ method?.call(this, newValue, oldValue);
719
+ }
720
+ upgrade() {
721
+ if (this.#parsed) {
722
+ return;
723
+ }
724
+ this.#parsed = true;
725
+ return this.ready();
726
+ }
727
+ }
728
+
729
+
730
+ const Templated = (SuperClass, template) => {
731
+ return class extends SuperClass {
732
+ async ready() {
647
733
  const slotted = Slots.from(this);
648
734
  const fragment = await Promise.resolve(this.render(slotted, template));
649
735
  this.innerHTML = '';
650
736
  if (fragment) {
651
737
  this.appendChild(fragment);
652
738
  }
653
- this.#rendered = true;
654
739
  }
655
740
  };
656
741
  };
@@ -659,40 +744,35 @@ var ful = (function (exports) {
659
744
 
660
745
  const all = [].concat(flags).concat(others || []);
661
746
 
662
- return class extends SuperClass {
747
+ const k = class extends SuperClass {
663
748
  static get observedAttributes() {
664
749
  return all;
665
750
  }
666
751
  constructor(...args) {
667
752
  super(...args);
668
753
  this.internals_ = this.internals_ || this.attachInternals();
669
- for (const flag of flags) {
670
- Object.defineProperty(this, flag, {
671
- get() {
672
- return this.internals_.states.has(`--${flag}`);
673
- },
674
- set(value) {
675
- //see https://developer.mozilla.org/en-US/docs/Web/API/CustomStateSet#using_double_dash_prefixed_idents
676
- if (Attributes.asBoolean(value)) {
677
- this.internals_.states.add(`--${flag}`);
678
- this.setAttribute(flag, '');
679
- return;
680
- }
681
- this.internals_.states.delete(`--${flag}`);
682
- this.removeAttribute(flag);
683
- }
684
- });
685
- }
686
- }
687
- attributeChangedCallback(name, oldValue, newValue) {
688
- if (oldValue === newValue) {
689
- return;
690
- }
691
- this[name] = newValue;
692
- const method = this[`on${name.charAt(0).toUpperCase()}${name.substr(1).toLowerCase()}Changed`];
693
- method?.call(this, newValue, oldValue);
694
754
  }
695
755
  };
756
+
757
+ for (const flag of flags) {
758
+ Object.defineProperty(k.prototype, flag, {
759
+ enumerable: true,
760
+ configurable: true,
761
+ get() {
762
+ return this.internals_.states.has(`--${flag}`);
763
+ },
764
+ set(value) {
765
+ //see https://developer.mozilla.org/en-US/docs/Web/API/CustomStateSet#using_double_dash_prefixed_idents
766
+ if (Attributes.asBoolean(value)) {
767
+ this.internals_.states.add(`--${flag}`);
768
+ return;
769
+ }
770
+ this.internals_.states.delete(`--${flag}`);
771
+ }
772
+ });
773
+ }
774
+
775
+ return k;
696
776
  };
697
777
 
698
778
  /* global Infinity, CSS */
@@ -709,10 +789,65 @@ var ful = (function (exports) {
709
789
  }, {});
710
790
  }
711
791
 
712
- class Form extends Templated(HTMLElement) {
713
- static IGNORED_CHILDREN_SELECTOR = '.d-none, [hidden]';
792
+ function providePath(result, path, value) {
793
+ const keys = path.split(".").map((k) => k.match(/^[0-9]+$/) ? +k : k);
794
+ let current = result;
795
+ let previous = null;
796
+ for (let i = 0; ; ++i) {
797
+ const ckey = keys[i];
798
+ const pkey = keys[i - 1];
799
+ if (Number.isInteger(ckey) && !Array.isArray(current)) {
800
+ if (previous !== null) {
801
+ previous[pkey] = current = [];
802
+ } else {
803
+ result = current = [];
804
+ }
805
+ }
806
+ if (i === keys.length - 1) {
807
+ //when value is undefined we only want to define the property if it's not defined
808
+ current[ckey] = value !== undefined ? value : (ckey in current ? current[ckey] : null);
809
+ return result;
810
+ }
811
+ if (current[ckey] === undefined) {
812
+ current[ckey] = {};
813
+ }
814
+ previous = current;
815
+ current = current[ckey];
816
+ }
817
+ }
714
818
 
715
- render(slotted, template) {
819
+ function extract(el) {
820
+ if (el.getAttribute('type') === 'radio') {
821
+ if (!el.checked) {
822
+ return undefined;
823
+ }
824
+ return el.dataset['fulBindType'] === 'boolean' ? el.value === 'true' : el.value;
825
+ }
826
+ if (el.getAttribute('type') === 'checkbox') {
827
+ return el.checked;
828
+ }
829
+ if (el.dataset['fulBindType'] === 'boolean') {
830
+ return !el.value ? null : el.value === 'true';
831
+ }
832
+ return el.value || null;
833
+ }
834
+
835
+ function mutate(el, raw) {
836
+ if (el.getAttribute('type') === 'radio') {
837
+ el.checked = el.getAttribute('value') === raw;
838
+ return;
839
+ }
840
+ if (el.getAttribute('type') === 'checkbox') {
841
+ el.checked = raw;
842
+ return;
843
+ }
844
+ el.value = raw;
845
+ }
846
+
847
+ class Form extends Templated(ParsedElement) {
848
+ static IGNORED_CHILDREN_SELECTOR = '.d-none, [hidden]';
849
+ static SCROLL_OFFSET = 50;
850
+ render(slotted) {
716
851
  const form = document.createElement('form');
717
852
  form.append(slotted.default);
718
853
  form.addEventListener('submit', async (e) => {
@@ -720,11 +855,11 @@ var ful = (function (exports) {
720
855
  this.spinner(true);
721
856
  try {
722
857
  if (this.submitter) {
723
- await this.submitter(this.getValues(), this);
858
+ await this.submitter(this.values, this);
724
859
  }
725
860
  } catch (e) {
726
861
  if (e instanceof Failure) {
727
- this.setErrors(e.problems);
862
+ this.errors = e.problems;
728
863
  return;
729
864
  }
730
865
  throw e;
@@ -735,21 +870,15 @@ var ful = (function (exports) {
735
870
  return form;
736
871
  }
737
872
  spinner(spin) {
738
- this.querySelectorAll('ful-spinner').forEach(el => {
739
- el.hidden = !spin;
740
- });
741
- this.querySelectorAll('[type=submit],[type=reset]').forEach(el => {
742
- el.disabled = spin;
743
- });
873
+ this.querySelectorAll('ful-spinner').forEach(el => el.hidden = !spin);
874
+ this.querySelectorAll('[type=submit],[type=reset]').forEach(el => el.disabled = spin);
744
875
  }
745
- setValues(values) {
746
- for (const [flattenedKey, value] of Object.entries(flatten(values, ''))) {
747
- Array.from(this.querySelectorAll(`[name='${CSS.escape(flattenedKey)}']`)).forEach((el) => {
748
- Form.mutate(el, value);
749
- });
876
+ set values(vs) {
877
+ for (const [flattenedKey, value] of Object.entries(flatten(vs, ''))) {
878
+ this.querySelectorAll(`[name='${CSS.escape(flattenedKey)}']`).forEach(el => mutate(el, value));
750
879
  }
751
880
  }
752
- getValues() {
881
+ get values() {
753
882
  return Array.from(this.querySelectorAll('[name]'))
754
883
  .filter((el) => {
755
884
  if (el.dataset['fulBindInclude'] === 'never') {
@@ -758,29 +887,30 @@ var ful = (function (exports) {
758
887
  return el.dataset['fulBindInclude'] === 'always' || el.closest(Form.IGNORED_CHILDREN_SELECTOR) === null;
759
888
  })
760
889
  .reduce((result, el) => {
761
- return Form.providePath(result, el.getAttribute('name'), Form.extract(el));
890
+ return providePath(result, el.getAttribute('name'), extract(el));
762
891
  }, {});
763
892
  }
764
- setErrors(errors) {
765
- this.clearErrors();
766
- errors
767
- .filter((e) => e.type === 'FIELD_ERROR' || e.type === 'INVALID_FORMAT')
768
- .forEach((e) => {
769
- const name = e.context.replace("[", ".").replace("].", ".");
770
- this.querySelectorAll(`[name='${CSS.escape(name)}'] [ful-validation-target],[name='${CSS.escape(name)}']:not(:has([ful-validation-target]))`)
771
- .forEach(input => input.classList.add('is-invalid'));
772
- this.querySelectorAll(`ful-field-error[field='${CSS.escape(name)}']`)
773
- .forEach(el => el.innerText = e.reason);
774
- });
775
- this.querySelectorAll("ful-errors")
776
- .forEach(el => {
777
- const globalErrors = errors.filter((e) => e.type !== 'FIELD_ERROR' && e.type !== 'INVALID_FORMAT');
778
- el.innerHTML = globalErrors.map(e => e.reason).join("\n");
779
- if (globalErrors.length !== 0) {
780
- el.removeAttribute('hidden');
781
- }
782
- });
783
-
893
+ set errors(es) {
894
+ const fieldErrors = es.filter((e) => e.type === 'FIELD_ERROR' || e.type === 'INVALID_FORMAT');
895
+ const globalErrors = es.filter((e) => e.type !== 'FIELD_ERROR' && e.type !== 'INVALID_FORMAT');
896
+ this.querySelectorAll('.is-invalid').forEach(el => el.classList.remove('is-invalid'));
897
+ this.querySelectorAll("ful-errors").forEach(el => {
898
+ el.innerHTML = '';
899
+ el.setAttribute('hidden', '');
900
+ });
901
+ fieldErrors.forEach((e) => {
902
+ const name = e.context.replace("[", ".").replace("].", ".");
903
+ const validationTargetsSelector = `[name='${CSS.escape(name)}'] [ful-validation-target],[name='${CSS.escape(name)}']:not(:has([ful-validation-target]))`;
904
+ this.querySelectorAll(validationTargetsSelector).forEach(input => input.classList.add('is-invalid'));
905
+ const fieldErrorsSelector = `ful-field-error[field='${CSS.escape(name)}']`;
906
+ this.querySelectorAll(fieldErrorsSelector).forEach(el => el.innerText = e.reason);
907
+ });
908
+ this.querySelectorAll("ful-errors").forEach(el => {
909
+ el.innerText = globalErrors.map(e => e.reason).join("\n");
910
+ if (globalErrors.length !== 0) {
911
+ el.removeAttribute('hidden');
912
+ }
913
+ });
784
914
  if (!this.hasAttribute('scroll-on-error')) {
785
915
  return;
786
916
  }
@@ -789,69 +919,7 @@ var ful = (function (exports) {
789
919
  .map(el => el.getBoundingClientRect().y + window.scrollY);
790
920
  const miny = Math.min(...ys);
791
921
  if (miny !== Infinity) {
792
- window.scroll(window.scrollX, miny > 100 ? miny - 100 : 0);
793
- }
794
- }
795
- clearErrors() {
796
- this.querySelectorAll('.is-invalid')
797
- .forEach(el => el.classList.remove('is-invalid'));
798
- this.querySelectorAll("ful-errors")
799
- .forEach(el => {
800
- el.innerHTML = '';
801
- el.setAttribute('hidden', '');
802
- });
803
- }
804
- static extract(el) {
805
- if (el.getAttribute('type') === 'radio') {
806
- if (!el.checked) {
807
- return undefined;
808
- }
809
- return el.dataset['fulBindType'] === 'boolean' ? el.value === 'true' : el.value;
810
- }
811
- if (el.getAttribute('type') === 'checkbox') {
812
- return el.checked;
813
- }
814
- if (el.dataset['fulBindType'] === 'boolean') {
815
- return !el.value ? null : el.value === 'true';
816
- }
817
- return el.value || null;
818
- }
819
- static mutate(el, raw) {
820
- if (el.getAttribute('type') === 'radio') {
821
- el.checked = el.getAttribute('value') === raw;
822
- return;
823
- }
824
- if (el.getAttribute('type') === 'checkbox') {
825
- el.checked = raw;
826
- return;
827
- }
828
- el.value = raw;
829
- }
830
-
831
- static providePath(result, path, value) {
832
- const keys = path.split(".").map((k) => k.match(/^[0-9]+$/) ? +k : k);
833
- let current = result;
834
- let previous = null;
835
- for (let i = 0; ; ++i) {
836
- const ckey = keys[i];
837
- const pkey = keys[i - 1];
838
- if (Number.isInteger(ckey) && !Array.isArray(current)) {
839
- if (previous !== null) {
840
- previous[pkey] = current = [];
841
- } else {
842
- result = current = [];
843
- }
844
- }
845
- if (i === keys.length - 1) {
846
- //when value is undefined we only want to define the property if it's not defined
847
- current[ckey] = value !== undefined ? value : (ckey in current ? current[ckey] : null);
848
- return result;
849
- }
850
- if (current[ckey] === undefined) {
851
- current[ckey] = {};
852
- }
853
- previous = current;
854
- current = current[ckey];
922
+ window.scroll(window.scrollX, miny > Form.SCROLL_OFFSET ? miny - Form.SCROLL_OFFSET : 0);
855
923
  }
856
924
  }
857
925
  static configure() {
@@ -875,7 +943,7 @@ var ful = (function (exports) {
875
943
  <ful-field-error data-tpl-if="name" data-tpl-field="name"></ful-field-error>
876
944
  `, ful_input_ec);
877
945
 
878
- class StatelessInput extends Templated(HTMLElement, ful_input_template_) {
946
+ class StatelessInput extends Templated(ParsedElement, ful_input_template_) {
879
947
  render(slotted, template) {
880
948
  const input = this.input = slotted.input = slotted.input || (() => {
881
949
  const el = document.createElement("input");
@@ -902,16 +970,9 @@ var ful = (function (exports) {
902
970
  return fragment;
903
971
  }
904
972
  get value() {
905
- if (!this.input) {
906
- return this.getAttribute('value');
907
- }
908
973
  return this.input.value;
909
974
  }
910
975
  set value(value) {
911
- if (!this.input) {
912
- //handled during rendering
913
- return;
914
- }
915
976
  this.input.value = value;
916
977
  }
917
978
  static configure() {
@@ -941,7 +1002,7 @@ var ful = (function (exports) {
941
1002
  `, ful_select_ec);
942
1003
 
943
1004
 
944
- class Select extends Templated(HTMLElement, ful_select_template_) {
1005
+ class Select extends Stateful(Templated(ParsedElement, ful_select_template_), [], ["value"]) {
945
1006
  constructor(tsConfig) {
946
1007
  super();
947
1008
  this.tsConfig = tsConfig;
@@ -1049,11 +1110,14 @@ var ful = (function (exports) {
1049
1110
  </label>
1050
1111
  </section>
1051
1112
  <ful-field-error data-tpl-if="name" data-tpl-field="name"></ful-field-error>
1113
+ <footer data-tpl-if="slotted.footer">
1114
+ {{{{ slotted.footer }}}}
1115
+ </footer>
1052
1116
  </fieldset>
1053
1117
  `, ful_radiogroup_ec);
1054
1118
 
1055
1119
 
1056
- class RadioGroup extends Stateful(Templated(HTMLElement, ful_radiougroup_template_), ['disabled']) {
1120
+ class RadioGroup extends Stateful(Templated(ParsedElement, ful_radiougroup_template_), ['disabled'], ['value']) {
1057
1121
  render(slotted, template) {
1058
1122
  const name = this.getAttribute('name') || Attributes.uid('ful-radiogroup');
1059
1123
  const radioEls = Array.from(slotted.default.querySelectorAll('ful-radio'));
@@ -1101,7 +1165,7 @@ var ful = (function (exports) {
1101
1165
  `, ful_spinner_ec);
1102
1166
 
1103
1167
 
1104
- class Spinner extends Templated(HTMLElement, ful_spinner_template_) {
1168
+ class Spinner extends Templated(ParsedElement, ful_spinner_template_) {
1105
1169
  render(slotted, template) {
1106
1170
  return template.render({ slotted });
1107
1171
  }
@@ -1161,7 +1225,7 @@ var ful = (function (exports) {
1161
1225
  cancelable: true
1162
1226
  }));
1163
1227
  }
1164
- moveTo = function (n) {
1228
+ moveTo(n) {
1165
1229
  this.progress.forEach(p => {
1166
1230
  const children = [...p.children];
1167
1231
  const current = children.filter(e => e.matches(".active"))[0];
@@ -1177,13 +1241,6 @@ var ful = (function (exports) {
1177
1241
  cancelable: true
1178
1242
  }));
1179
1243
  }
1180
- static custom(tagName, configuration) {
1181
- customElements.define(tagName, class extends Wizard {
1182
- constructor() {
1183
- super(configuration);
1184
- }
1185
- });
1186
- }
1187
1244
  static configure() {
1188
1245
  return Wizard.custom('ful-wizard');
1189
1246
  }
@@ -1201,6 +1258,8 @@ var ful = (function (exports) {
1201
1258
  exports.HttpClient = HttpClient;
1202
1259
  exports.Input = Input;
1203
1260
  exports.LocalStorage = LocalStorage;
1261
+ exports.Nodes = Nodes;
1262
+ exports.ParsedElement = ParsedElement;
1204
1263
  exports.RadioGroup = RadioGroup;
1205
1264
  exports.Select = Select;
1206
1265
  exports.SessionStorage = SessionStorage;
@@ -1208,6 +1267,7 @@ var ful = (function (exports) {
1208
1267
  exports.Spinner = Spinner;
1209
1268
  exports.Stateful = Stateful;
1210
1269
  exports.StatelessInput = StatelessInput;
1270
+ exports.SyncEvent = SyncEvent;
1211
1271
  exports.Templated = Templated;
1212
1272
  exports.VersionedStorage = VersionedStorage;
1213
1273
  exports.Wizard = Wizard;