@optionfactory/ful 0.46.0 → 0.48.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.mjs CHANGED
@@ -665,6 +665,38 @@ class Nodes {
665
665
  }
666
666
  }
667
667
 
668
+ class TemplateRegistry {
669
+ #idToFragment = {};
670
+ #idToTemplate = {};
671
+ #ec;
672
+ put(k, fragment) {
673
+ if (this.#ec) {
674
+ this.#idToTemplate[k] = ftl.Template.fromFragment(fragment, ec);
675
+ return;
676
+ }
677
+ this.#idToFragment[k] = fragment;
678
+ }
679
+ get(k) {
680
+ if (!this.#ec) {
681
+ throw new Error("evaluationContext is not configured");
682
+ }
683
+ const tpl = this.#idToTemplate[k];
684
+ if (!tpl) {
685
+ throw new Error(`missing template: '${k}'`);
686
+ }
687
+ return tpl;
688
+ }
689
+ configure(ec) {
690
+ this.#ec = ec;
691
+ for (const [k, fragment] of Object.entries(this.#idToFragment)) {
692
+ delete this.#idToFragment[k];
693
+ this.#idToTemplate[k] = ftl.Template.fromFragment(fragment, ec);
694
+ }
695
+ }
696
+ }
697
+
698
+ const templates = new TemplateRegistry();
699
+
668
700
  class UpgradeQueue {
669
701
  #q = [];
670
702
  constructor() {
@@ -683,83 +715,85 @@ class UpgradeQueue {
683
715
 
684
716
  const upgradeQueue = new UpgradeQueue();
685
717
 
686
- class ParsedElement extends HTMLElement {
687
- #parsed;
688
- connectedCallback() {
689
- if (this.#parsed) {
690
- return;
691
- }
692
- if (this.ownerDocument.readyState === 'complete' || Nodes.isParsed(this)) {
693
- upgradeQueue.enqueue(this);
694
- return;
695
- }
696
- this.ownerDocument.addEventListener('DOMContentLoaded', () => {
697
- observer.disconnect();
698
- upgradeQueue.enqueue(this);
699
- });
700
- const observer = new MutationObserver(() => {
701
- if (!Nodes.isParsed(this)) {
702
- return;
703
- }
704
- observer.disconnect();
705
- upgradeQueue.enqueue(this);
706
- });
707
- observer.observe(this.parentNode, { childList: true, subtree: true });
708
- }
709
- attributeChangedCallback(name, oldValue, newValue) {
710
- if (!this.#parsed || oldValue === newValue) {
711
- return;
712
- }
713
- this[name] = newValue;
714
- }
715
- upgrade() {
716
- if (this.#parsed) {
717
- return;
718
- }
719
- this.#parsed = true;
720
- return this.ready();
721
- }
722
- }
723
-
718
+ const ParsedElement = (flags, others) => {
724
719
 
725
- const Templated = (SuperClass, template) => {
726
- return class extends SuperClass {
727
- async ready() {
728
- const slotted = Slots.from(this);
729
- const fragment = await Promise.resolve(this.render(slotted, template));
730
- this.replaceChildren(fragment);
731
- }
732
- };
733
- };
734
-
735
- const Stateful = (SuperClass, flags, others) => {
720
+ const observed_flags = flags || [];
721
+ const observed_others = others || [];
722
+ const observed = [].concat(observed_flags).concat(observed_others);
736
723
 
737
- const all = [].concat(flags).concat(others || []);
738
-
739
- const k = class extends SuperClass {
724
+ const k = class extends HTMLElement {
740
725
  static get observedAttributes() {
741
- return all;
726
+ return observed;
742
727
  }
728
+ #parsed;
729
+ #internals;
743
730
  constructor(...args) {
744
731
  super(...args);
745
- this.internals_ = this.internals_ || this.attachInternals();
732
+ this.#internals = this.attachInternals();
733
+ }
734
+ get internals() {
735
+ return this.#internals;
736
+ }
737
+ connectedCallback() {
738
+ if (this.#parsed) {
739
+ return;
740
+ }
741
+ if (this.ownerDocument.readyState === 'complete' || Nodes.isParsed(this)) {
742
+ upgradeQueue.enqueue(this);
743
+ return;
744
+ }
745
+ this.ownerDocument.addEventListener('DOMContentLoaded', () => {
746
+ observer.disconnect();
747
+ upgradeQueue.enqueue(this);
748
+ });
749
+ const observer = new MutationObserver(() => {
750
+ if (!Nodes.isParsed(this)) {
751
+ return;
752
+ }
753
+ observer.disconnect();
754
+ upgradeQueue.enqueue(this);
755
+ });
756
+ observer.observe(this.parentNode, { childList: true, subtree: true });
746
757
  }
758
+ attributeChangedCallback(name, oldValue, newValue) {
759
+ if (!this.#parsed || oldValue === newValue) {
760
+ return;
761
+ }
762
+ this[name] = newValue;
763
+ }
764
+ async upgrade() {
765
+ if (this.#parsed) {
766
+ return;
767
+ }
768
+ this.#parsed = true;
769
+ await this.render();
770
+ for (const flag of observed_flags) {
771
+ if(this.hasAttribute(flag)){
772
+ this[flag] = true;
773
+ }
774
+ }
775
+ for (const other of observed_others) {
776
+ if(this.hasAttribute(other)){
777
+ this[other] = this.getAttribute(other);
778
+ }
779
+ }
780
+ }
747
781
  };
748
782
 
749
- for (const flag of flags) {
783
+ for (const flag of observed_flags) {
750
784
  Object.defineProperty(k.prototype, flag, {
751
785
  enumerable: true,
752
786
  configurable: true,
753
787
  get() {
754
- return this.internals_.states.has(`--${flag}`);
788
+ return this.internals.states.has(`--${flag}`);
755
789
  },
756
790
  set(value) {
757
791
  //see https://developer.mozilla.org/en-US/docs/Web/API/CustomStateSet#using_double_dash_prefixed_idents
758
792
  if (Attributes.asBoolean(value)) {
759
- this.internals_.states.add(`--${flag}`);
793
+ this.internals.states.add(`--${flag}`);
760
794
  return;
761
795
  }
762
- this.internals_.states.delete(`--${flag}`);
796
+ this.internals.states.delete(`--${flag}`);
763
797
  }
764
798
  });
765
799
  }
@@ -836,12 +870,12 @@ function mutate(el, raw) {
836
870
  el.value = raw;
837
871
  }
838
872
 
839
- class Form extends Templated(ParsedElement) {
873
+ class Form extends ParsedElement() {
840
874
  static IGNORED_CHILDREN_SELECTOR = '.d-none, [hidden]';
841
875
  static SCROLL_OFFSET = 50;
842
- render(slotted) {
876
+ render() {
843
877
  const form = document.createElement('form');
844
- form.append(slotted.default);
878
+ form.replaceChildren(...this.childNodes);
845
879
  form.addEventListener('submit', async (e) => {
846
880
  e.preventDefault();
847
881
  this.spinner(true);
@@ -859,7 +893,7 @@ class Form extends Templated(ParsedElement) {
859
893
  this.spinner(false);
860
894
  }
861
895
  });
862
- return form;
896
+ this.replaceChildren(form);
863
897
  }
864
898
  spinner(spin) {
865
899
  this.querySelectorAll('ful-spinner').forEach(el => el.hidden = !spin);
@@ -916,11 +950,7 @@ class Form extends Templated(ParsedElement) {
916
950
  }
917
951
  }
918
952
 
919
- const ful_input_ec = globalThis.ec || ftl.EvaluationContext.configure({
920
-
921
- });
922
-
923
- const ful_input_template_ = globalThis.ful_input_template || ftl.Template.fromHtml(`
953
+ templates.put('ful-input', Fragments.fromHtml(`
924
954
  <label data-tpl-for="id" class="form-label">{{{{ slotted.default }}}}</label>
925
955
  <div class="input-group">
926
956
  <span data-tpl-if="slotted.ibefore" class="input-group-text">{{{{ slotted.ibefore }}}}</span>
@@ -930,33 +960,31 @@ const ful_input_template_ = globalThis.ful_input_template || ftl.Template.fromHt
930
960
  <span data-tpl-if="slotted.iafter" class="input-group-text">{{{{ slotted.iafter }}}}</span>
931
961
  </div>
932
962
  <ful-field-error data-tpl-if="name" data-tpl-field="name"></ful-field-error>
933
- `, ful_input_ec);
934
-
935
- class StatelessInput extends Templated(ParsedElement, ful_input_template_) {
936
- render(slotted, template) {
937
- const input = this.input = slotted.input = slotted.input || (() => {
938
- const el = document.createElement("input");
939
- el.classList.add("form-control");
940
- return el;
941
- })();
942
- input.setAttribute('ful-validation-target', '');
943
-
944
- const id = input.getAttribute('id') || this.getAttribute('input-id') || Attributes.uid('ful-input');
945
- Attributes.forward('input-', this, slotted.input);
946
- Attributes.defaultValue(slotted.input, "id", id);
947
- Attributes.defaultValue(slotted.input, "type", "text");
948
- Attributes.defaultValue(slotted.input, "placeholder", " ");
949
- const name = this.getAttribute('name');
950
- return template.render({ id, name, slotted });
951
- }
952
-
953
- }
963
+ `));
964
+
965
+
966
+ const makeInputFragment = (el, slotted) => {
967
+ const input = el.input = slotted.input = slotted.input || (() => {
968
+ const el = document.createElement("input");
969
+ el.classList.add("form-control");
970
+ return el;
971
+ })();
972
+ input.setAttribute('ful-validation-target', '');
973
+
974
+ const id = input.getAttribute('id') || el.getAttribute('input-id') || Attributes.uid('ful-input');
975
+ Attributes.forward('input-', el, slotted.input);
976
+ Attributes.defaultValue(slotted.input, "id", id);
977
+ Attributes.defaultValue(slotted.input, "type", "text");
978
+ Attributes.defaultValue(slotted.input, "placeholder", " ");
979
+ const name = el.getAttribute('name');
980
+ return templates.get('ful-input').render(el, { id, name, slotted });
981
+ };
954
982
 
955
- class Input extends Stateful(StatelessInput, [], ['value']) {
956
- render(slotted, template) {
957
- const fragment = super.render(slotted, template);
958
- this.input.value = this.getAttribute('value');
959
- return fragment;
983
+ class Input extends ParsedElement([], ['value']) {
984
+ render() {
985
+ const slotted = Slots.from(el);
986
+ const fragment = makeInputFragment(this, slotted);
987
+ this.replaceChildren(fragment);
960
988
  }
961
989
  get value() {
962
990
  return this.input.value;
@@ -970,11 +998,8 @@ class Input extends Stateful(StatelessInput, [], ['value']) {
970
998
  * <script src="tom-select.complete.js"></script>
971
999
  * <link href="tom-select.bootstrap5.css" rel="stylesheet" />
972
1000
  */
973
- const ful_select_ec = globalThis.ec || ftl.EvaluationContext.configure({
974
-
975
- });
976
1001
 
977
- const ful_select_template_ = globalThis.ful_select_template || ftl.Template.fromHtml(`
1002
+ templates.put('ful-select', Fragments.fromHtml(`
978
1003
  <label data-tpl-for="tsId" class="form-label">{{{{ slotted.default }}}}</label>
979
1004
  {{{{ input }}}}
980
1005
  <div class="input-group">
@@ -985,15 +1010,17 @@ const ful_select_template_ = globalThis.ful_select_template || ftl.Template.from
985
1010
  <span data-tpl-if="slotted.iafter" class="input-group-text">{{{{ slotted.iafter }}}}</span>
986
1011
  </div>
987
1012
  <ful-field-error data-tpl-if="name" data-tpl-field="name"></ful-field-error>
988
- `, ful_select_ec);
1013
+ `));
989
1014
 
990
1015
 
991
- class Select extends Stateful(Templated(ParsedElement, ful_select_template_), [], ["value"]) {
1016
+ class Select extends ParsedElement([], ["value"]) {
992
1017
  constructor(tsConfig) {
993
1018
  super();
994
1019
  this.tsConfig = tsConfig;
995
1020
  }
996
- render(slotted, template) {
1021
+ render() {
1022
+ const slotted = Slots.from(this);
1023
+
997
1024
  const type = this.getAttribute("type") || 'local';
998
1025
  const remote = type != 'local';
999
1026
  const loadOnce = this.getAttribute('load') != 'always';
@@ -1021,7 +1048,6 @@ class Select extends Stateful(Templated(ParsedElement, ful_select_template_), []
1021
1048
  }
1022
1049
  };
1023
1050
 
1024
-
1025
1051
  this._remote = remote;
1026
1052
  // we need to await this load in setValue when remote is configured and the option
1027
1053
  // is not loaded yet.
@@ -1036,7 +1062,7 @@ class Select extends Stateful(Templated(ParsedElement, ful_select_template_), []
1036
1062
  const type = query && query.hasOwnProperty('byId') ? 'id' : 'query';
1037
1063
  const qvalue = type === 'id' ? query.byId : query;
1038
1064
  const data = await (this.loader ? this.loader(qvalue, type) : []);
1039
- if(type !== 'id'){
1065
+ if (type !== 'id') {
1040
1066
  this.loaded = true;
1041
1067
  }
1042
1068
  callback(data);
@@ -1050,12 +1076,12 @@ class Select extends Stateful(Templated(ParsedElement, ful_select_template_), []
1050
1076
  } : {}, tsDefaultConfig, this.tsConfig));
1051
1077
  //we remove the input to move it
1052
1078
  input.remove();
1053
- return template.render({ id, tsId, name, input, slotted });
1079
+ templates.get('ful-select').renderTo(this, { id, tsId, name, input, slotted });
1054
1080
  }
1055
1081
  set value(v) {
1056
1082
  (async () => {
1057
- if(this._remote){
1058
- await this._unwrappedRemoteLoad({byId: v}, this.ts.loadCallback.bind(this.ts));
1083
+ if (this._remote) {
1084
+ await this._unwrappedRemoteLoad({ byId: v }, this.ts.loadCallback.bind(this.ts));
1059
1085
  }
1060
1086
  this.ts.setValue(v);
1061
1087
  })();
@@ -1066,11 +1092,7 @@ class Select extends Stateful(Templated(ParsedElement, ful_select_template_), []
1066
1092
  }
1067
1093
  }
1068
1094
 
1069
- const ful_radiogroup_ec = globalThis.ec || ftl.EvaluationContext.configure({
1070
-
1071
- });
1072
-
1073
- const ful_radiougroup_template_ = globalThis.ful_radiogroup_template || ftl.Template.fromHtml(`
1095
+ templates.put('ful-radio-group', Fragments.fromHtml(`
1074
1096
  <fieldset>
1075
1097
  <legend class="form-label">
1076
1098
  {{{{ slotted.default }}}}
@@ -1089,11 +1111,12 @@ const ful_radiougroup_template_ = globalThis.ful_radiogroup_template || ftl.Temp
1089
1111
  {{{{ slotted.footer }}}}
1090
1112
  </footer>
1091
1113
  </fieldset>
1092
- `, ful_radiogroup_ec);
1114
+ `));
1093
1115
 
1094
1116
 
1095
- class RadioGroup extends Stateful(Templated(ParsedElement, ful_radiougroup_template_), ['disabled'], ['value']) {
1096
- render(slotted, template) {
1117
+ class RadioGroup extends ParsedElement(['disabled'], ['value']) {
1118
+ render() {
1119
+ const slotted = Slots.from(this);
1097
1120
  const name = this.getAttribute('name') || Attributes.uid('ful-radiogroup');
1098
1121
  const radioEls = Array.from(slotted.default.querySelectorAll('ful-radio'));
1099
1122
  const inputsAndLabels = radioEls.map(el => {
@@ -1108,38 +1131,32 @@ class RadioGroup extends Stateful(Templated(ParsedElement, ful_radiougroup_templ
1108
1131
  return [input, label];
1109
1132
  });
1110
1133
  radioEls.forEach(el => el.remove());
1111
-
1112
- const fragment = template.render({
1134
+ templates.get('ful-radio-group').renderTo(this, {
1113
1135
  name: name,
1114
1136
  slotted: slotted,
1115
1137
  inputsAndLabels: inputsAndLabels
1116
1138
  });
1117
- return fragment;
1118
1139
  }
1119
1140
  get value() {
1120
1141
  const checked = this.querySelector('input[type=radio]:checked');
1121
1142
  return checked ? checked.value : null;
1122
1143
  }
1123
- set value(value){
1144
+ set value(value) {
1124
1145
  this.querySelector(`input[type=radio][value=${CSS.escape(value)}]`).checked = true;
1125
1146
  }
1126
1147
  }
1127
1148
 
1128
- const ful_spinner_ec = globalThis.ec || ftl.EvaluationContext.configure({
1129
-
1130
- });
1131
-
1132
- const ful_spinner_template_ = globalThis.ful_spinner_template || ftl.Template.fromHtml(`
1149
+ templates.put('ful-spinner', Fragments.fromHtml(`
1133
1150
  <div class="ful-spinner-wrapper">
1134
1151
  <div class="ful-spinner-text">{{{{ slotted.default }}}}</div>
1135
1152
  <div class="ful-spinner-icon"></div>
1136
1153
  </div>
1137
- `, ful_spinner_ec);
1154
+ `));
1138
1155
 
1139
-
1140
- class Spinner extends Templated(ParsedElement, ful_spinner_template_) {
1141
- render(slotted, template) {
1142
- return template.render({ slotted });
1156
+ class Spinner extends ParsedElement() {
1157
+ render() {
1158
+ const slotted = Slots.from(this);
1159
+ templates.get('ful-spinner').renderTo(this, { slotted });
1143
1160
  }
1144
1161
  }
1145
1162
 
@@ -1216,7 +1233,8 @@ class Wizard extends HTMLElement {
1216
1233
  }
1217
1234
 
1218
1235
  class CustomElements {
1219
- static configure() {
1236
+ static configure(ec) {
1237
+ templates.configure(ec);
1220
1238
  customElements.define('ful-spinner', Spinner);
1221
1239
  customElements.define('ful-input', Input);
1222
1240
  customElements.define('ful-radio-group', RadioGroup);
@@ -1225,5 +1243,5 @@ class CustomElements {
1225
1243
  }
1226
1244
  }
1227
1245
 
1228
- export { Attributes, AuthorizationCodeFlow, AuthorizationCodeFlowInterceptor, AuthorizationCodeFlowSession, Base64, CustomElements, Failure, Form, Fragments, Hex, HttpClient, Input, LocalStorage, Nodes, ParsedElement, RadioGroup, Select, SessionStorage, Slots, Spinner, Stateful, StatelessInput, SyncEvent, Templated, VersionedStorage, Wizard, jsonPatch, jsonPost, jsonPut, jsonRequest, timing };
1246
+ export { Attributes, AuthorizationCodeFlow, AuthorizationCodeFlowInterceptor, AuthorizationCodeFlowSession, Base64, CustomElements, Failure, Form, Fragments, Hex, HttpClient, Input, LocalStorage, Nodes, ParsedElement, RadioGroup, Select, SessionStorage, Slots, Spinner, SyncEvent, TemplateRegistry, VersionedStorage, Wizard, jsonPatch, jsonPost, jsonPut, jsonRequest, makeInputFragment, templates, timing };
1229
1247
  //# sourceMappingURL=ful.mjs.map