@asteby/storefront-react 0.17.0 → 0.18.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.
@@ -8,6 +8,7 @@ type AddressFieldKey = 'zip' | 'province' | 'city' | 'neighborhood' | 'address1'
8
8
  type ContactFieldKey = 'email' | 'phone' | 'first_name' | 'last_name';
9
9
  type AddressFieldsMessages = Record<AddressFieldKey | ContactFieldKey, string> & {
10
10
  provincePlaceholder: string;
11
+ neighborhoodPlaceholder: string;
11
12
  address1Hint: string;
12
13
  referenceHint: string;
13
14
  emailHint: string;
@@ -74,6 +75,12 @@ type AddressFieldsProps = Common & {
74
75
  * Never overwrites what the buyer typed.
75
76
  */
76
77
  lookupPostalCode?: (zip: string) => Promise<PostalCodeInfo | null>;
78
+ /**
79
+ * How to offer the colonias when the postal code has SEVERAL: `'datalist'` (default, suggestions on a text field) or
80
+ * `'select'` (a real list; the field becomes a `<select>` of those colonias, unless the buyer already typed one that
81
+ * is not in the list, which is kept as text). With exactly one colonia it is filled in either mode.
82
+ */
83
+ neighborhoodList?: 'datalist' | 'select';
77
84
  /** Show the colonia field: `true` (default, optional), `'required'`, or `false`. */
78
85
  neighborhood?: boolean | 'required';
79
86
  /** Show the delivery references field (default true, optional). */
@@ -8,6 +8,7 @@ type AddressFieldKey = 'zip' | 'province' | 'city' | 'neighborhood' | 'address1'
8
8
  type ContactFieldKey = 'email' | 'phone' | 'first_name' | 'last_name';
9
9
  type AddressFieldsMessages = Record<AddressFieldKey | ContactFieldKey, string> & {
10
10
  provincePlaceholder: string;
11
+ neighborhoodPlaceholder: string;
11
12
  address1Hint: string;
12
13
  referenceHint: string;
13
14
  emailHint: string;
@@ -74,6 +75,12 @@ type AddressFieldsProps = Common & {
74
75
  * Never overwrites what the buyer typed.
75
76
  */
76
77
  lookupPostalCode?: (zip: string) => Promise<PostalCodeInfo | null>;
78
+ /**
79
+ * How to offer the colonias when the postal code has SEVERAL: `'datalist'` (default, suggestions on a text field) or
80
+ * `'select'` (a real list; the field becomes a `<select>` of those colonias, unless the buyer already typed one that
81
+ * is not in the list, which is kept as text). With exactly one colonia it is filled in either mode.
82
+ */
83
+ neighborhoodList?: 'datalist' | 'select';
77
84
  /** Show the colonia field: `true` (default, optional), `'required'`, or `false`. */
78
85
  neighborhood?: boolean | 'required';
79
86
  /** Show the delivery references field (default true, optional). */
package/dist/checkout.js CHANGED
@@ -21,6 +21,7 @@ var ES = {
21
21
  address2: "N\xFAmero interior o departamento",
22
22
  reference: "Referencias para la entrega",
23
23
  provincePlaceholder: "Elige tu estado",
24
+ neighborhoodPlaceholder: "Elige tu colonia",
24
25
  address1Hint: "Por ejemplo: Av. Insurgentes Sur 1234",
25
26
  referenceHint: "Entre qu\xE9 calles, color de la fachada\u2026",
26
27
  emailHint: "Te enviamos aqu\xED la confirmaci\xF3n y el seguimiento.",
@@ -40,6 +41,7 @@ var EN = {
40
41
  address2: "Apartment, suite",
41
42
  reference: "Delivery notes",
42
43
  provincePlaceholder: "Choose your state",
44
+ neighborhoodPlaceholder: "Choose your neighborhood",
43
45
  address1Hint: "For example: 1234 Main St",
44
46
  referenceHint: "Cross streets, building color\u2026",
45
47
  emailHint: "We send the confirmation and tracking here.",
@@ -96,6 +98,9 @@ function AddressFields(props) {
96
98
  const listId = useId();
97
99
  const [colonias, setColonias] = useState([]);
98
100
  const [filled, setFilled] = useState(false);
101
+ const [coloniaSelect, setColoniaSelect] = useState(false);
102
+ const coloniaSelectRef = useRef(false);
103
+ const coloniaCarry = useRef(null);
99
104
  const lastZip = useRef(null);
100
105
  const zipRef = useRef(null);
101
106
  const lookupRef = useRef(lookupPostalCode);
@@ -135,18 +140,18 @@ function AddressFields(props) {
135
140
  enqueue(() => {
136
141
  if (lastZip.current !== zip) return;
137
142
  const fill2 = (name, value, lock) => {
138
- const el = form?.elements.namedItem(name);
139
- if (!el || !value || "length" in el && !(el instanceof HTMLSelectElement)) return false;
140
- if (el.dataset.sfLocked === "1") return false;
141
- if (el.dataset.sfUserEdited === "1" || el.ownerDocument.activeElement === el && el.value) return false;
142
- if (el.value && el.dataset.sfAutofilled !== "1") return false;
143
- if (el instanceof HTMLSelectElement && ![...el.options].some((o) => o.value === value)) return false;
144
- el.value = value;
145
- el.dataset.sfAutofilled = "1";
143
+ const el2 = form?.elements.namedItem(name);
144
+ if (!el2 || !value || "length" in el2 && !(el2 instanceof HTMLSelectElement)) return false;
145
+ if (el2.dataset.sfLocked === "1") return false;
146
+ if (el2.dataset.sfUserEdited === "1" || el2.ownerDocument.activeElement === el2 && el2.value) return false;
147
+ if (el2.value && el2.dataset.sfAutofilled !== "1") return false;
148
+ if (el2 instanceof HTMLSelectElement && ![...el2.options].some((o) => o.value === value)) return false;
149
+ el2.value = value;
150
+ el2.dataset.sfAutofilled = "1";
146
151
  const event = new Event("input", { bubbles: true });
147
152
  Object.defineProperty(event, ADDRESS_FILL_EVENT_FLAG, { value: true });
148
- el.dispatchEvent(event);
149
- if (lock) lockField(el);
153
+ el2.dispatchEvent(event);
154
+ if (lock) lockField(el2);
150
155
  return true;
151
156
  };
152
157
  const a = fill2(n("province"), info.province, lockProvinceOnFill);
@@ -154,13 +159,42 @@ function AddressFields(props) {
154
159
  const only = info.neighborhoods?.length === 1 ? info.neighborhoods[0] : null;
155
160
  const c = showNeighborhood ? fill2(n("neighborhood"), only) : false;
156
161
  setColonias(info.neighborhoods ?? []);
157
- setFilled(a || b || c);
162
+ const list = info.neighborhoods ?? [];
163
+ const nameEl = form?.elements.namedItem(n("neighborhood"));
164
+ const el = nameEl && !("length" in nameEl && !(nameEl instanceof HTMLSelectElement)) ? nameEl : null;
165
+ const typed = el ? el.value : "";
166
+ const wantSelect = props.neighborhoodList === "select" && showNeighborhood !== false && list.length > 1 && (!typed || list.includes(typed));
167
+ let carried = false;
168
+ if (showNeighborhood && wantSelect !== coloniaSelectRef.current) {
169
+ const userEdited = el?.dataset.sfUserEdited === "1";
170
+ const value = only && !userEdited ? only : wantSelect ? list.includes(typed) ? typed : "" : typed;
171
+ coloniaCarry.current = { value, autofilled: !userEdited && (!!only || el?.dataset.sfAutofilled === "1"), userEdited };
172
+ carried = !!only && !userEdited;
173
+ }
174
+ coloniaSelectRef.current = wantSelect;
175
+ setColoniaSelect(wantSelect);
176
+ setFilled(a || b || c || carried);
158
177
  });
159
178
  },
160
179
  () => {
161
180
  }
162
181
  );
163
182
  };
183
+ useEffect(() => {
184
+ const carry = coloniaCarry.current;
185
+ coloniaCarry.current = null;
186
+ if (!carry || !carry.value) return;
187
+ const form = zipRef.current?.closest("form");
188
+ const el = form?.elements.namedItem(n("neighborhood"));
189
+ if (!el || "length" in el && !(el instanceof HTMLSelectElement)) return;
190
+ if (el instanceof HTMLSelectElement && ![...el.options].some((o) => o.value === carry.value)) return;
191
+ el.value = carry.value;
192
+ if (carry.userEdited) el.dataset.sfUserEdited = "1";
193
+ else if (carry.autofilled) el.dataset.sfAutofilled = "1";
194
+ const event = new Event("input", { bubbles: true });
195
+ Object.defineProperty(event, ADDRESS_FILL_EVENT_FLAG, { value: true });
196
+ el.dispatchEvent(event);
197
+ }, [coloniaSelect]);
164
198
  useEffect(() => {
165
199
  const zip = defaults?.zip;
166
200
  if (zip) runLookup(zip);
@@ -194,7 +228,24 @@ function AddressFields(props) {
194
228
  provinces.map((p) => /* @__PURE__ */ jsx("option", { value: p.name, children: p.name }, p.code ?? p.name))
195
229
  ] }) : /* @__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") }),
196
230
  /* @__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") }),
197
- showNeighborhood ? /* @__PURE__ */ jsx(
231
+ showNeighborhood && coloniaSelect ? /* @__PURE__ */ jsxs(
232
+ CheckoutField,
233
+ {
234
+ ...common,
235
+ as: "select",
236
+ name: n("neighborhood"),
237
+ label: m.neighborhood,
238
+ autoComplete: "address-level3",
239
+ optional: showNeighborhood !== "required",
240
+ defaultValue: colonias.includes(defaults?.neighborhood ?? "") ? defaults?.neighborhood ?? "" : "",
241
+ error: errors[n("neighborhood")] ?? null,
242
+ classNames: field(cx, "half"),
243
+ children: [
244
+ /* @__PURE__ */ jsx("option", { value: "", disabled: showNeighborhood === "required", children: m.neighborhoodPlaceholder }),
245
+ colonias.map((c) => /* @__PURE__ */ jsx("option", { value: c, children: c }, c))
246
+ ]
247
+ }
248
+ ) : showNeighborhood ? /* @__PURE__ */ jsx(
198
249
  CheckoutField,
199
250
  {
200
251
  ...common,
@@ -205,10 +256,10 @@ function AddressFields(props) {
205
256
  defaultValue: defaults?.neighborhood ?? "",
206
257
  error: errors[n("neighborhood")] ?? null,
207
258
  classNames: field(cx, "half"),
208
- ...colonias.length ? { list: listId } : {}
259
+ ...colonias.length && !coloniaSelect ? { list: listId } : {}
209
260
  }
210
261
  ) : null,
211
- colonias.length ? /* @__PURE__ */ jsx("datalist", { id: listId, children: colonias.map((c) => /* @__PURE__ */ jsx("option", { value: c }, c)) }) : null,
262
+ colonias.length && !coloniaSelect ? /* @__PURE__ */ jsx("datalist", { id: listId, children: colonias.map((c) => /* @__PURE__ */ jsx("option", { value: c }, c)) }) : null,
212
263
  /* @__PURE__ */ jsx("p", { role: "status", "aria-live": "polite", "data-sf-zip-status": true, style: filled ? void 0 : { position: "absolute", width: 1, height: 1, overflow: "hidden", clip: "rect(0 0 0 0)", whiteSpace: "nowrap" }, className: filled ? join(cx.hint, cx.wide) : void 0, children: filled ? m.lookupFilled : "" }),
213
264
  /* @__PURE__ */ jsx(CheckoutField, { ...common, name: n("address1"), label: m.address1, autoComplete: "address-line1", defaultValue: defaults?.address1 ?? "", error: errors[n("address1")] ?? null, hint: m.address1Hint, classNames: field(cx, "wide") }),
214
265
  /* @__PURE__ */ jsx(CheckoutField, { ...common, name: n("address2"), label: m.address2, autoComplete: "address-line2", optional: true, defaultValue: defaults?.address2 ?? "", error: errors[n("address2")] ?? null, classNames: field(cx, "half") }),