@asteby/storefront-react 0.21.0 → 0.23.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.
@@ -14,6 +14,10 @@ type AddressFieldsMessages = Record<AddressFieldKey | ContactFieldKey, string> &
14
14
  emailHint: string;
15
15
  phoneHint: string;
16
16
  lookupFilled: string;
17
+ /** Tooltip and note of a state the postal code defined (locked). */
18
+ provinceLocked: string;
19
+ /** Discreet action that releases the locked state so the buyer can fix the postal code. */
20
+ changePostalCode: string;
17
21
  };
18
22
  declare function addressFieldsMessages(locale?: string | null, overrides?: Partial<AddressFieldsMessages> | null): AddressFieldsMessages;
19
23
  type FieldLayoutClassNames = CheckoutFieldClassNames & {
@@ -88,8 +92,11 @@ type AddressFieldsProps = Common & {
88
92
  /**
89
93
  * Once `lookupPostalCode` fills the state field (never over what the buyer already typed), lock it: a plain
90
94
  * `<input>` turns read-only; a `<select>` (from `provinces`) turns disabled with a hidden field that keeps
91
- * submitting its value. The buyer corrects the state by correcting the postal code. Default false: the filled
92
- * state stays as editable as any other field. Marked with `data-sf-locked="1"` for the shop's own styling.
95
+ * submitting its value. The state is the postal code's: the field shows a lock, the tooltip `provinceLocked` and a
96
+ * discreet `changePostalCode` action; editing the postal code releases the lock and clears what the lookup filled
97
+ * (also when the new code resolves nothing), so a stale state is never left behind. Default TRUE (the server
98
+ * validates the pair again; set `false` to keep the filled state as editable as any field). Marked with
99
+ * `data-sf-locked="1"` for the shop's own styling.
93
100
  */
94
101
  lockProvinceOnFill?: boolean;
95
102
  /** Rendered after the fields (e.g. "save this address"). */
@@ -14,6 +14,10 @@ type AddressFieldsMessages = Record<AddressFieldKey | ContactFieldKey, string> &
14
14
  emailHint: string;
15
15
  phoneHint: string;
16
16
  lookupFilled: string;
17
+ /** Tooltip and note of a state the postal code defined (locked). */
18
+ provinceLocked: string;
19
+ /** Discreet action that releases the locked state so the buyer can fix the postal code. */
20
+ changePostalCode: string;
17
21
  };
18
22
  declare function addressFieldsMessages(locale?: string | null, overrides?: Partial<AddressFieldsMessages> | null): AddressFieldsMessages;
19
23
  type FieldLayoutClassNames = CheckoutFieldClassNames & {
@@ -88,8 +92,11 @@ type AddressFieldsProps = Common & {
88
92
  /**
89
93
  * Once `lookupPostalCode` fills the state field (never over what the buyer already typed), lock it: a plain
90
94
  * `<input>` turns read-only; a `<select>` (from `provinces`) turns disabled with a hidden field that keeps
91
- * submitting its value. The buyer corrects the state by correcting the postal code. Default false: the filled
92
- * state stays as editable as any other field. Marked with `data-sf-locked="1"` for the shop's own styling.
95
+ * submitting its value. The state is the postal code's: the field shows a lock, the tooltip `provinceLocked` and a
96
+ * discreet `changePostalCode` action; editing the postal code releases the lock and clears what the lookup filled
97
+ * (also when the new code resolves nothing), so a stale state is never left behind. Default TRUE (the server
98
+ * validates the pair again; set `false` to keep the filled state as editable as any field). Marked with
99
+ * `data-sf-locked="1"` for the shop's own styling.
93
100
  */
94
101
  lockProvinceOnFill?: boolean;
95
102
  /** Rendered after the fields (e.g. "save this address"). */
package/dist/checkout.js CHANGED
@@ -28,7 +28,9 @@ var ES = {
28
28
  referenceHint: "Entre qu\xE9 calles, color de la fachada\u2026",
29
29
  emailHint: "Te enviamos aqu\xED la confirmaci\xF3n y el seguimiento.",
30
30
  phoneHint: "Solo para la paqueter\xEDa, por si necesita contactarte.",
31
- lookupFilled: "Completamos estado y ciudad con tu c\xF3digo postal."
31
+ lookupFilled: "Completamos estado y ciudad con tu c\xF3digo postal.",
32
+ provinceLocked: "Definido por tu c\xF3digo postal",
33
+ changePostalCode: "Cambiar c\xF3digo postal"
32
34
  };
33
35
  var EN = {
34
36
  email: "Email",
@@ -48,7 +50,9 @@ var EN = {
48
50
  referenceHint: "Cross streets, building color\u2026",
49
51
  emailHint: "We send the confirmation and tracking here.",
50
52
  phoneHint: "Only for the carrier, in case they need to reach you.",
51
- lookupFilled: "We filled in state and city from your postal code."
53
+ lookupFilled: "We filled in state and city from your postal code.",
54
+ provinceLocked: "Set by your postal code",
55
+ changePostalCode: "Change postal code"
52
56
  };
53
57
  function addressFieldsMessages(locale, overrides) {
54
58
  const base = (locale ?? "es-MX").toLowerCase().startsWith("en") ? EN : ES;
@@ -74,8 +78,10 @@ function lockField(el) {
74
78
  if (el instanceof HTMLSelectElement) {
75
79
  const name = el.getAttribute("name");
76
80
  if (name) {
81
+ el.dataset.sfName = name;
77
82
  const hidden = document.createElement("input");
78
83
  hidden.type = "hidden";
84
+ hidden.dataset.sfLockMirror = "1";
79
85
  hidden.name = name;
80
86
  hidden.value = el.value;
81
87
  el.insertAdjacentElement("afterend", hidden);
@@ -86,12 +92,27 @@ function lockField(el) {
86
92
  el.readOnly = true;
87
93
  }
88
94
  }
95
+ function unlockField(el) {
96
+ if (el.dataset.sfLocked !== "1") return;
97
+ delete el.dataset.sfLocked;
98
+ el.removeAttribute("aria-readonly");
99
+ el.removeAttribute("title");
100
+ if (el instanceof HTMLSelectElement) {
101
+ const mirror = el.nextElementSibling;
102
+ if (mirror instanceof HTMLInputElement && mirror.dataset.sfLockMirror === "1") mirror.remove();
103
+ if (el.dataset.sfName) el.setAttribute("name", el.dataset.sfName);
104
+ delete el.dataset.sfName;
105
+ el.disabled = false;
106
+ } else {
107
+ el.readOnly = false;
108
+ }
109
+ }
89
110
  var ADDRESS_FILL_EVENT_FLAG = "sfSynthetic";
90
111
  function isAddressFillEvent(event) {
91
112
  return event[ADDRESS_FILL_EVENT_FLAG] === true;
92
113
  }
93
114
  function AddressFields(props) {
94
- const { defaults, names = {}, errors = {}, countryCode, provinces, postalCode, lookupPostalCode, lockProvinceOnFill, locale, messages, fieldMessages: fm, classNames: cx = {}, children } = props;
115
+ const { defaults, names = {}, errors = {}, countryCode, provinces, postalCode, lookupPostalCode, lockProvinceOnFill = true, locale, messages, fieldMessages: fm, classNames: cx = {}, children } = props;
95
116
  const showNeighborhood = props.neighborhood ?? true;
96
117
  const showReference = props.reference ?? true;
97
118
  const m = addressFieldsMessages(locale, messages);
@@ -100,6 +121,7 @@ function AddressFields(props) {
100
121
  const listId = useId();
101
122
  const [colonias, setColonias] = useState([]);
102
123
  const [filled, setFilled] = useState(false);
124
+ const [provinceLocked, setProvinceLocked] = useState(false);
103
125
  const [coloniaSelect, setColoniaSelect] = useState(false);
104
126
  const coloniaSelectRef = useRef(false);
105
127
  const coloniaCarry = useRef(null);
@@ -130,6 +152,31 @@ function AddressFields(props) {
130
152
  form.removeEventListener("change", mark, true);
131
153
  };
132
154
  }, []);
155
+ const releaseLookup = () => {
156
+ const form = zipRef.current?.closest("form");
157
+ if (!form) return;
158
+ const locked = form.querySelector('[data-sf-locked="1"]');
159
+ const touched = [];
160
+ if (locked) {
161
+ unlockField(locked);
162
+ touched.push(locked);
163
+ }
164
+ for (const key of ["province", "city", "neighborhood"]) {
165
+ const el = form.elements.namedItem(n(key));
166
+ if (el && !("length" in el && !(el instanceof HTMLSelectElement)) && !touched.includes(el)) touched.push(el);
167
+ }
168
+ for (const el of touched) {
169
+ if (el.dataset.sfAutofilled !== "1") continue;
170
+ el.value = "";
171
+ delete el.dataset.sfAutofilled;
172
+ const event = new Event("input", { bubbles: true });
173
+ Object.defineProperty(event, ADDRESS_FILL_EVENT_FLAG, { value: true });
174
+ el.dispatchEvent(event);
175
+ }
176
+ lastZip.current = null;
177
+ setProvinceLocked(false);
178
+ setFilled(false);
179
+ };
133
180
  const runLookup = (zip) => {
134
181
  const lookup = lookupRef.current;
135
182
  if (!lookup || !zip || zip === lastZip.current) return;
@@ -153,7 +200,11 @@ function AddressFields(props) {
153
200
  const event = new Event("input", { bubbles: true });
154
201
  Object.defineProperty(event, ADDRESS_FILL_EVENT_FLAG, { value: true });
155
202
  el2.dispatchEvent(event);
156
- if (lock) lockField(el2);
203
+ if (lock) {
204
+ lockField(el2);
205
+ el2.title = m.provinceLocked;
206
+ setProvinceLocked(true);
207
+ }
157
208
  return true;
158
209
  };
159
210
  const a = fill2(n("province"), info.province, lockProvinceOnFill);
@@ -201,6 +252,22 @@ function AddressFields(props) {
201
252
  const zip = defaults?.zip;
202
253
  if (zip) runLookup(zip);
203
254
  }, []);
255
+ const hasLock = (root) => !!root?.closest("form")?.querySelector('[data-sf-locked="1"]');
256
+ const changeZip = () => {
257
+ releaseLookup();
258
+ const zipEl = zipRef.current?.querySelector("input");
259
+ zipEl?.focus();
260
+ zipEl?.select();
261
+ };
262
+ const provinceHint = provinceLocked ? /* @__PURE__ */ jsxs("span", { "data-sf-province-locked": "", title: m.provinceLocked, children: [
263
+ /* @__PURE__ */ jsxs("svg", { "aria-hidden": "true", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { display: "inline-block", verticalAlign: "-1px", marginRight: 4 }, children: [
264
+ /* @__PURE__ */ jsx("rect", { x: "4", y: "11", width: "16", height: "10", rx: "2" }),
265
+ /* @__PURE__ */ jsx("path", { d: "M8 11V7a4 4 0 0 1 8 0v4" })
266
+ ] }),
267
+ m.provinceLocked,
268
+ " \xB7 ",
269
+ /* @__PURE__ */ jsx("button", { type: "button", "data-sf-change-zip": "", onClick: changeZip, style: { background: "none", border: 0, padding: 0, font: "inherit", color: "inherit", textDecoration: "underline", cursor: "pointer" }, children: m.changePostalCode })
270
+ ] }) : void 0;
204
271
  const numericZip = postalCode?.inputMode === "numeric";
205
272
  return /* @__PURE__ */ jsxs(Fragment, { children: [
206
273
  /* @__PURE__ */ jsx("div", { ref: zipRef, className: join(cx.half), "data-sf-zip": true, children: /* @__PURE__ */ jsx(
@@ -220,15 +287,16 @@ function AddressFields(props) {
220
287
  classNames: { ...cx, root: cx.root },
221
288
  onInput: (e) => {
222
289
  const v = e.currentTarget.value.trim();
290
+ if (v !== lastZip.current && hasLock(zipRef.current)) releaseLookup();
223
291
  if (postalCode?.maxLength && v.length === postalCode.maxLength) runLookup(v);
224
292
  },
225
293
  onBlur: (e) => runLookup(e.currentTarget.value.trim())
226
294
  }
227
295
  ) }),
228
- provinces && provinces.length ? /* @__PURE__ */ jsxs(CheckoutField, { ...common, as: "select", name: n("province"), label: m.province, autoComplete: "address-level1", defaultValue: defaults?.province ?? "", error: errors[n("province")] ?? null, classNames: field(cx, "half"), children: [
296
+ provinces && provinces.length ? /* @__PURE__ */ jsxs(CheckoutField, { ...common, as: "select", name: n("province"), label: m.province, autoComplete: "address-level1", defaultValue: defaults?.province ?? "", error: errors[n("province")] ?? null, classNames: field(cx, "half"), hint: provinceHint, children: [
229
297
  /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: m.provincePlaceholder }),
230
298
  provinces.map((p) => /* @__PURE__ */ jsx("option", { value: p.name, children: p.name }, p.code ?? p.name))
231
- ] }) : /* @__PURE__ */ jsx(CheckoutField, { ...common, name: n("province"), label: m.province, autoComplete: "address-level1", defaultValue: defaults?.province ?? "", error: errors[n("province")] ?? null, classNames: field(cx, "half") }),
299
+ ] }) : /* @__PURE__ */ jsx(CheckoutField, { ...common, name: n("province"), label: m.province, autoComplete: "address-level1", defaultValue: defaults?.province ?? "", error: errors[n("province")] ?? null, classNames: field(cx, "half"), hint: provinceHint }),
232
300
  /* @__PURE__ */ jsx(CheckoutField, { ...common, name: n("city"), label: m.city, autoComplete: "address-level2", defaultValue: defaults?.city ?? "", error: errors[n("city")] ?? null, classNames: field(cx, "half") }),
233
301
  showNeighborhood && coloniaSelect ? /* @__PURE__ */ jsxs(
234
302
  CheckoutField,