@optionfactory/ful 0.43.0 → 0.45.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,83 @@ 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
+ }
718
+ upgrade() {
719
+ if (this.#parsed) {
720
+ return;
721
+ }
722
+ this.#parsed = true;
723
+ return this.ready();
724
+ }
725
+ }
726
+
727
+
728
+ const Templated = (SuperClass, template) => {
729
+ return class extends SuperClass {
730
+ async ready() {
647
731
  const slotted = Slots.from(this);
648
732
  const fragment = await Promise.resolve(this.render(slotted, template));
649
733
  this.innerHTML = '';
650
734
  if (fragment) {
651
735
  this.appendChild(fragment);
652
736
  }
653
- this.#rendered = true;
654
737
  }
655
738
  };
656
739
  };
@@ -659,40 +742,35 @@ var ful = (function (exports) {
659
742
 
660
743
  const all = [].concat(flags).concat(others || []);
661
744
 
662
- return class extends SuperClass {
745
+ const k = class extends SuperClass {
663
746
  static get observedAttributes() {
664
747
  return all;
665
748
  }
666
749
  constructor(...args) {
667
750
  super(...args);
668
751
  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
752
  }
695
753
  };
754
+
755
+ for (const flag of flags) {
756
+ Object.defineProperty(k.prototype, flag, {
757
+ enumerable: true,
758
+ configurable: true,
759
+ get() {
760
+ return this.internals_.states.has(`--${flag}`);
761
+ },
762
+ set(value) {
763
+ //see https://developer.mozilla.org/en-US/docs/Web/API/CustomStateSet#using_double_dash_prefixed_idents
764
+ if (Attributes.asBoolean(value)) {
765
+ this.internals_.states.add(`--${flag}`);
766
+ return;
767
+ }
768
+ this.internals_.states.delete(`--${flag}`);
769
+ }
770
+ });
771
+ }
772
+
773
+ return k;
696
774
  };
697
775
 
698
776
  /* global Infinity, CSS */
@@ -709,10 +787,65 @@ var ful = (function (exports) {
709
787
  }, {});
710
788
  }
711
789
 
712
- class Form extends Templated(HTMLElement) {
713
- static IGNORED_CHILDREN_SELECTOR = '.d-none, [hidden]';
790
+ function providePath(result, path, value) {
791
+ const keys = path.split(".").map((k) => k.match(/^[0-9]+$/) ? +k : k);
792
+ let current = result;
793
+ let previous = null;
794
+ for (let i = 0; ; ++i) {
795
+ const ckey = keys[i];
796
+ const pkey = keys[i - 1];
797
+ if (Number.isInteger(ckey) && !Array.isArray(current)) {
798
+ if (previous !== null) {
799
+ previous[pkey] = current = [];
800
+ } else {
801
+ result = current = [];
802
+ }
803
+ }
804
+ if (i === keys.length - 1) {
805
+ //when value is undefined we only want to define the property if it's not defined
806
+ current[ckey] = value !== undefined ? value : (ckey in current ? current[ckey] : null);
807
+ return result;
808
+ }
809
+ if (current[ckey] === undefined) {
810
+ current[ckey] = {};
811
+ }
812
+ previous = current;
813
+ current = current[ckey];
814
+ }
815
+ }
714
816
 
715
- render(slotted, template) {
817
+ function extract(el) {
818
+ if (el.getAttribute('type') === 'radio') {
819
+ if (!el.checked) {
820
+ return undefined;
821
+ }
822
+ return el.dataset['fulBindType'] === 'boolean' ? el.value === 'true' : el.value;
823
+ }
824
+ if (el.getAttribute('type') === 'checkbox') {
825
+ return el.checked;
826
+ }
827
+ if (el.dataset['fulBindType'] === 'boolean') {
828
+ return !el.value ? null : el.value === 'true';
829
+ }
830
+ return el.value || null;
831
+ }
832
+
833
+ function mutate(el, raw) {
834
+ if (el.getAttribute('type') === 'radio') {
835
+ el.checked = el.getAttribute('value') === raw;
836
+ return;
837
+ }
838
+ if (el.getAttribute('type') === 'checkbox') {
839
+ el.checked = raw;
840
+ return;
841
+ }
842
+ el.value = raw;
843
+ }
844
+
845
+ class Form extends Templated(ParsedElement) {
846
+ static IGNORED_CHILDREN_SELECTOR = '.d-none, [hidden]';
847
+ static SCROLL_OFFSET = 50;
848
+ render(slotted) {
716
849
  const form = document.createElement('form');
717
850
  form.append(slotted.default);
718
851
  form.addEventListener('submit', async (e) => {
@@ -720,11 +853,11 @@ var ful = (function (exports) {
720
853
  this.spinner(true);
721
854
  try {
722
855
  if (this.submitter) {
723
- await this.submitter(this.getValues(), this);
856
+ await this.submitter(this.values, this);
724
857
  }
725
858
  } catch (e) {
726
859
  if (e instanceof Failure) {
727
- this.setErrors(e.problems);
860
+ this.errors = e.problems;
728
861
  return;
729
862
  }
730
863
  throw e;
@@ -735,21 +868,15 @@ var ful = (function (exports) {
735
868
  return form;
736
869
  }
737
870
  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
- });
871
+ this.querySelectorAll('ful-spinner').forEach(el => el.hidden = !spin);
872
+ this.querySelectorAll('[type=submit],[type=reset]').forEach(el => el.disabled = spin);
744
873
  }
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
- });
874
+ set values(vs) {
875
+ for (const [flattenedKey, value] of Object.entries(flatten(vs, ''))) {
876
+ this.querySelectorAll(`[name='${CSS.escape(flattenedKey)}']`).forEach(el => mutate(el, value));
750
877
  }
751
878
  }
752
- getValues() {
879
+ get values() {
753
880
  return Array.from(this.querySelectorAll('[name]'))
754
881
  .filter((el) => {
755
882
  if (el.dataset['fulBindInclude'] === 'never') {
@@ -758,29 +885,30 @@ var ful = (function (exports) {
758
885
  return el.dataset['fulBindInclude'] === 'always' || el.closest(Form.IGNORED_CHILDREN_SELECTOR) === null;
759
886
  })
760
887
  .reduce((result, el) => {
761
- return Form.providePath(result, el.getAttribute('name'), Form.extract(el));
888
+ return providePath(result, el.getAttribute('name'), extract(el));
762
889
  }, {});
763
890
  }
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
-
891
+ set errors(es) {
892
+ const fieldErrors = es.filter((e) => e.type === 'FIELD_ERROR' || e.type === 'INVALID_FORMAT');
893
+ const globalErrors = es.filter((e) => e.type !== 'FIELD_ERROR' && e.type !== 'INVALID_FORMAT');
894
+ this.querySelectorAll('.is-invalid').forEach(el => el.classList.remove('is-invalid'));
895
+ this.querySelectorAll("ful-errors").forEach(el => {
896
+ el.innerHTML = '';
897
+ el.setAttribute('hidden', '');
898
+ });
899
+ fieldErrors.forEach((e) => {
900
+ const name = e.context.replace("[", ".").replace("].", ".");
901
+ const validationTargetsSelector = `[name='${CSS.escape(name)}'] [ful-validation-target],[name='${CSS.escape(name)}']:not(:has([ful-validation-target]))`;
902
+ this.querySelectorAll(validationTargetsSelector).forEach(input => input.classList.add('is-invalid'));
903
+ const fieldErrorsSelector = `ful-field-error[field='${CSS.escape(name)}']`;
904
+ this.querySelectorAll(fieldErrorsSelector).forEach(el => el.innerText = e.reason);
905
+ });
906
+ this.querySelectorAll("ful-errors").forEach(el => {
907
+ el.innerText = globalErrors.map(e => e.reason).join("\n");
908
+ if (globalErrors.length !== 0) {
909
+ el.removeAttribute('hidden');
910
+ }
911
+ });
784
912
  if (!this.hasAttribute('scroll-on-error')) {
785
913
  return;
786
914
  }
@@ -789,74 +917,9 @@ var ful = (function (exports) {
789
917
  .map(el => el.getBoundingClientRect().y + window.scrollY);
790
918
  const miny = Math.min(...ys);
791
919
  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];
920
+ window.scroll(window.scrollX, miny > Form.SCROLL_OFFSET ? miny - Form.SCROLL_OFFSET : 0);
855
921
  }
856
922
  }
857
- static configure() {
858
- customElements.define('ful-form', Form);
859
- }
860
923
  }
861
924
 
862
925
  const ful_input_ec = globalThis.ec || ftl.EvaluationContext.configure({
@@ -875,7 +938,7 @@ var ful = (function (exports) {
875
938
  <ful-field-error data-tpl-if="name" data-tpl-field="name"></ful-field-error>
876
939
  `, ful_input_ec);
877
940
 
878
- class StatelessInput extends Templated(HTMLElement, ful_input_template_) {
941
+ class StatelessInput extends Templated(ParsedElement, ful_input_template_) {
879
942
  render(slotted, template) {
880
943
  const input = this.input = slotted.input = slotted.input || (() => {
881
944
  const el = document.createElement("input");
@@ -902,21 +965,11 @@ var ful = (function (exports) {
902
965
  return fragment;
903
966
  }
904
967
  get value() {
905
- if (!this.input) {
906
- return this.getAttribute('value');
907
- }
908
968
  return this.input.value;
909
969
  }
910
970
  set value(value) {
911
- if (!this.input) {
912
- //handled during rendering
913
- return;
914
- }
915
971
  this.input.value = value;
916
972
  }
917
- static configure() {
918
- customElements.define('ful-input', Input);
919
- }
920
973
  }
921
974
 
922
975
  /**
@@ -941,7 +994,7 @@ var ful = (function (exports) {
941
994
  `, ful_select_ec);
942
995
 
943
996
 
944
- class Select extends Templated(HTMLElement, ful_select_template_) {
997
+ class Select extends Stateful(Templated(ParsedElement, ful_select_template_), [], ["value"]) {
945
998
  constructor(tsConfig) {
946
999
  super();
947
1000
  this.tsConfig = tsConfig;
@@ -1017,17 +1070,6 @@ var ful = (function (exports) {
1017
1070
  const v = this.ts.getValue();
1018
1071
  return v === '' ? null : v;
1019
1072
  }
1020
- static custom(tagName, configuration) {
1021
- customElements.define(tagName, class extends Select {
1022
- constructor() {
1023
- super(configuration);
1024
- }
1025
- });
1026
- }
1027
- static configure() {
1028
- return Select.custom('ful-select');
1029
- }
1030
-
1031
1073
  }
1032
1074
 
1033
1075
  const ful_radiogroup_ec = globalThis.ec || ftl.EvaluationContext.configure({
@@ -1056,7 +1098,7 @@ var ful = (function (exports) {
1056
1098
  `, ful_radiogroup_ec);
1057
1099
 
1058
1100
 
1059
- class RadioGroup extends Stateful(Templated(HTMLElement, ful_radiougroup_template_), ['disabled']) {
1101
+ class RadioGroup extends Stateful(Templated(ParsedElement, ful_radiougroup_template_), ['disabled'], ['value']) {
1060
1102
  render(slotted, template) {
1061
1103
  const name = this.getAttribute('name') || Attributes.uid('ful-radiogroup');
1062
1104
  const radioEls = Array.from(slotted.default.querySelectorAll('ful-radio'));
@@ -1087,9 +1129,6 @@ var ful = (function (exports) {
1087
1129
  set value(value){
1088
1130
  this.querySelector(`input[type=radio][value=${CSS.escape(value)}]`).checked = true;
1089
1131
  }
1090
- static configure() {
1091
- customElements.define('ful-radio-group', RadioGroup);
1092
- }
1093
1132
  }
1094
1133
 
1095
1134
  const ful_spinner_ec = globalThis.ec || ftl.EvaluationContext.configure({
@@ -1104,13 +1143,10 @@ var ful = (function (exports) {
1104
1143
  `, ful_spinner_ec);
1105
1144
 
1106
1145
 
1107
- class Spinner extends Templated(HTMLElement, ful_spinner_template_) {
1146
+ class Spinner extends Templated(ParsedElement, ful_spinner_template_) {
1108
1147
  render(slotted, template) {
1109
1148
  return template.render({ slotted });
1110
1149
  }
1111
- static configure() {
1112
- customElements.define('ful-spinner', Spinner);
1113
- }
1114
1150
  }
1115
1151
 
1116
1152
  class Wizard extends HTMLElement {
@@ -1164,7 +1200,7 @@ var ful = (function (exports) {
1164
1200
  cancelable: true
1165
1201
  }));
1166
1202
  }
1167
- moveTo = function (n) {
1203
+ moveTo(n) {
1168
1204
  this.progress.forEach(p => {
1169
1205
  const children = [...p.children];
1170
1206
  const current = children.filter(e => e.matches(".active"))[0];
@@ -1180,23 +1216,27 @@ var ful = (function (exports) {
1180
1216
  cancelable: true
1181
1217
  }));
1182
1218
  }
1183
- static custom(tagName, configuration) {
1184
- customElements.define(tagName, class extends Wizard {
1185
- constructor() {
1186
- super(configuration);
1187
- }
1188
- });
1189
- }
1190
1219
  static configure() {
1191
1220
  return Wizard.custom('ful-wizard');
1192
1221
  }
1193
1222
  }
1194
1223
 
1224
+ class CustomElements {
1225
+ static configure() {
1226
+ customElements.define('ful-spinner', Spinner);
1227
+ customElements.define('ful-input', Input);
1228
+ customElements.define('ful-radio-group', RadioGroup);
1229
+ customElements.define('ful-select', Select);
1230
+ customElements.define('ful-form', Form);
1231
+ }
1232
+ }
1233
+
1195
1234
  exports.Attributes = Attributes;
1196
1235
  exports.AuthorizationCodeFlow = AuthorizationCodeFlow;
1197
1236
  exports.AuthorizationCodeFlowInterceptor = AuthorizationCodeFlowInterceptor;
1198
1237
  exports.AuthorizationCodeFlowSession = AuthorizationCodeFlowSession;
1199
1238
  exports.Base64 = Base64;
1239
+ exports.CustomElements = CustomElements;
1200
1240
  exports.Failure = Failure;
1201
1241
  exports.Form = Form;
1202
1242
  exports.Fragments = Fragments;
@@ -1204,6 +1244,8 @@ var ful = (function (exports) {
1204
1244
  exports.HttpClient = HttpClient;
1205
1245
  exports.Input = Input;
1206
1246
  exports.LocalStorage = LocalStorage;
1247
+ exports.Nodes = Nodes;
1248
+ exports.ParsedElement = ParsedElement;
1207
1249
  exports.RadioGroup = RadioGroup;
1208
1250
  exports.Select = Select;
1209
1251
  exports.SessionStorage = SessionStorage;
@@ -1211,6 +1253,7 @@ var ful = (function (exports) {
1211
1253
  exports.Spinner = Spinner;
1212
1254
  exports.Stateful = Stateful;
1213
1255
  exports.StatelessInput = StatelessInput;
1256
+ exports.SyncEvent = SyncEvent;
1214
1257
  exports.Templated = Templated;
1215
1258
  exports.VersionedStorage = VersionedStorage;
1216
1259
  exports.Wizard = Wizard;