form-11926121.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /*!
  2. * All material copyright ESRI, All Rights Reserved, unless otherwise specified.
  3. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details.
  4. * v1.0.0-beta.82
  5. */
  6. 'use strict';
  7. const dom = require('./dom-9ac0341c.js');
  8. const index = require('./index-5c65e149.js');
  9. /**
  10. * Exported for testing purposes.
  11. */
  12. const hiddenFormInputSlotName = "hidden-form-input";
  13. function isCheckable(component) {
  14. return "checked" in component;
  15. }
  16. const onFormResetMap = new WeakMap();
  17. const formComponentSet = new WeakSet();
  18. function hasRegisteredFormComponentParent(form, formComponentEl) {
  19. // we use events as a way to test for nested form-associated components across shadow bounds
  20. const formComponentRegisterEventName = "calciteInternalFormComponentRegister";
  21. let hasRegisteredFormComponentParent = false;
  22. form.addEventListener(formComponentRegisterEventName, (event) => {
  23. hasRegisteredFormComponentParent = event
  24. .composedPath()
  25. .some((element) => formComponentSet.has(element));
  26. event.stopPropagation();
  27. }, { once: true });
  28. formComponentEl.dispatchEvent(new CustomEvent(formComponentRegisterEventName, {
  29. bubbles: true,
  30. composed: true
  31. }));
  32. return hasRegisteredFormComponentParent;
  33. }
  34. /**
  35. * Helper to submit a form.
  36. */
  37. function submitForm(component) {
  38. var _a;
  39. (_a = component.formEl) === null || _a === void 0 ? void 0 : _a.requestSubmit();
  40. }
  41. /**
  42. * Helper to reset a form.
  43. */
  44. function resetForm(component) {
  45. var _a;
  46. (_a = component.formEl) === null || _a === void 0 ? void 0 : _a.reset();
  47. }
  48. /**
  49. * Helper to set up form interactions on connectedCallback.
  50. */
  51. function connectForm(component) {
  52. const { el, value } = component;
  53. const form = dom.closestElementCrossShadowBoundary(el, "form");
  54. if (!form || hasRegisteredFormComponentParent(form, el)) {
  55. return;
  56. }
  57. component.formEl = form;
  58. component.defaultValue = value;
  59. if (isCheckable(component)) {
  60. component.defaultChecked = component.checked;
  61. }
  62. const boundOnFormReset = (component.onFormReset || onFormReset).bind(component);
  63. form.addEventListener("reset", boundOnFormReset);
  64. onFormResetMap.set(component.el, boundOnFormReset);
  65. formComponentSet.add(el);
  66. }
  67. function onFormReset() {
  68. if (isCheckable(this)) {
  69. this.checked = this.defaultChecked;
  70. return;
  71. }
  72. this.value = this.defaultValue;
  73. }
  74. /**
  75. * Helper to tear down form interactions on disconnectedCallback.
  76. */
  77. function disconnectForm(component) {
  78. const { el, formEl } = component;
  79. if (!formEl) {
  80. return;
  81. }
  82. const boundOnFormReset = onFormResetMap.get(el);
  83. formEl.removeEventListener("reset", boundOnFormReset);
  84. onFormResetMap.delete(el);
  85. component.formEl = null;
  86. formComponentSet.delete(el);
  87. }
  88. /**
  89. * Helper for setting the default value on initialization after connectedCallback.
  90. *
  91. * Note that this is only needed if the default value cannot be determined on connectedCallback.
  92. */
  93. function afterConnectDefaultValueSet(component, value) {
  94. component.defaultValue = value;
  95. }
  96. /**
  97. * Helper for maintaining a form-associated's hidden input in sync with the component.
  98. *
  99. * Based on Ionic's approach: https://github.com/ionic-team/ionic-framework/blob/e4bf052794af9aac07f887013b9250d2a045eba3/core/src/utils/helpers.ts#L198
  100. */
  101. function syncHiddenFormInput(component) {
  102. const { el, formEl, name, value } = component;
  103. const { ownerDocument } = el;
  104. const inputs = el.querySelectorAll(`input[slot="${hiddenFormInputSlotName}"]`);
  105. if (!formEl || !name) {
  106. inputs.forEach((input) => input.remove());
  107. return;
  108. }
  109. const values = Array.isArray(value) ? value : [value];
  110. const extra = [];
  111. const seen = new Set();
  112. inputs.forEach((input) => {
  113. const valueMatch = values.find((val) =>
  114. /* intentional non-strict equality check */
  115. val == input.value);
  116. if (valueMatch != null) {
  117. seen.add(valueMatch);
  118. defaultSyncHiddenFormInput(component, input, valueMatch);
  119. }
  120. else {
  121. extra.push(input);
  122. }
  123. });
  124. let docFrag;
  125. values.forEach((value) => {
  126. if (seen.has(value)) {
  127. return;
  128. }
  129. let input = extra.pop();
  130. if (!input) {
  131. input = ownerDocument.createElement("input");
  132. input.slot = hiddenFormInputSlotName;
  133. }
  134. if (!docFrag) {
  135. docFrag = ownerDocument.createDocumentFragment();
  136. }
  137. docFrag.append(input);
  138. defaultSyncHiddenFormInput(component, input, value);
  139. });
  140. if (docFrag) {
  141. el.append(docFrag);
  142. }
  143. extra.forEach((input) => input.remove());
  144. }
  145. function defaultSyncHiddenFormInput(component, input, value) {
  146. var _a;
  147. const { defaultValue, disabled, name, required } = component;
  148. // keep in sync to prevent losing reset value
  149. input.defaultValue = defaultValue;
  150. input.disabled = disabled;
  151. input.name = name;
  152. input.required = required;
  153. input.tabIndex = -1;
  154. if (isCheckable(component)) {
  155. // keep in sync to prevent losing reset value
  156. input.defaultChecked = component.defaultChecked;
  157. // heuristic to support default/on mode from https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default-on
  158. input.value = component.checked ? value || "on" : "";
  159. // we disable the component when not checked to avoid having its value submitted
  160. if (!disabled && !component.checked) {
  161. input.disabled = true;
  162. }
  163. }
  164. else {
  165. input.value = value || "";
  166. }
  167. (_a = component.syncHiddenFormInput) === null || _a === void 0 ? void 0 : _a.call(component, input);
  168. }
  169. /**
  170. * Helper to render the slot for form-associated component's hidden input.
  171. *
  172. * If the component has a default slot, this must be placed at the bottom of the component's root container to ensure it is the last child.
  173. *
  174. * render(): VNode {
  175. * <Host>
  176. * <div class={CSS.container}>
  177. * // ...
  178. * <HiddenFormInputSlot component={this} />
  179. * </div>
  180. * </Host>
  181. * }
  182. *
  183. * Note that the hidden-form-input Sass mixin must be added to the component's style to apply specific styles.
  184. */
  185. const HiddenFormInputSlot = ({ component }) => {
  186. syncHiddenFormInput(component);
  187. return index.h("slot", { name: hiddenFormInputSlotName });
  188. };
  189. exports.HiddenFormInputSlot = HiddenFormInputSlot;
  190. exports.afterConnectDefaultValueSet = afterConnectDefaultValueSet;
  191. exports.connectForm = connectForm;
  192. exports.disconnectForm = disconnectForm;
  193. exports.resetForm = resetForm;
  194. exports.submitForm = submitForm;