@arkyc/widget 1.1.0 → 1.2.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/ui.mjs CHANGED
@@ -3,6 +3,7 @@ import { Camera } from "./capture.mjs";
3
3
  import { DEFAULT_TUNING, createDefaultFaceAnalyzer, isSelfieReady, makeChallengeDetector } from "./face.mjs";
4
4
  import { DEFAULT_DOCUMENT_TUNING, createDefaultDocumentAnalyzer, documentGuidance } from "./document.mjs";
5
5
  import { countries } from "./countries.mjs";
6
+ import { createDom } from "./h.mjs";
6
7
  //#region src/ui.ts
7
8
  const DOCUMENT_LABELS = {
8
9
  passport: "Passport",
@@ -99,36 +100,36 @@ var WidgetView = class {
99
100
  timers = [];
100
101
  /** Extra teardown run on each re-render (cancels in-flight detection loops). */
101
102
  cleanups = [];
103
+ /** DOM hyperscript helpers (`h`/`append`/`field`) bound to this view's document (see {@link createDom}). */
104
+ dom;
102
105
  constructor(doc, theme, handlers, nav = globalThis.navigator, analyzer = createDefaultFaceAnalyzer(), tuning = DEFAULT_TUNING, docAnalyzer = createDefaultDocumentAnalyzer(), docTuning = DEFAULT_DOCUMENT_TUNING) {
103
106
  this.doc = doc;
104
107
  this.theme = theme;
105
108
  this.handlers = handlers;
109
+ this.dom = createDom(doc);
106
110
  this.camera = new Camera(doc, nav);
107
111
  this.analyzer = analyzer;
108
112
  this.tuning = tuning;
109
113
  this.docAnalyzer = docAnalyzer;
110
114
  this.docTuning = docTuning;
111
- this.root = this.el("div", { class: "arkyc-root" });
112
- this.styleEl = this.el("style", { text: theme.stylesheet() });
115
+ this.root = this.dom.h("div", { class: "arkyc-root" });
116
+ this.styleEl = this.dom.h("style", { text: theme.stylesheet() });
113
117
  this.root.appendChild(this.styleEl);
114
- const card = this.el("div", { class: "arkyc-card" });
115
- const header = this.el("div", { class: "arkyc-header" });
116
- this.brandEl = this.el("div", { class: "arkyc-brand" });
118
+ this.brandEl = this.dom.h("div", { class: "arkyc-brand" });
117
119
  this.fillBrand();
118
- header.appendChild(this.brandEl);
119
- const close = this.el("button", {
120
+ this.body = this.dom.h("div", { class: "arkyc-body" });
121
+ this.footer = this.dom.h("div", { class: "arkyc-footer" });
122
+ const header = this.dom.h("div", { class: "arkyc-header" }, [this.brandEl, this.dom.h("button", {
120
123
  class: "arkyc-close",
121
124
  html: "×",
122
- "aria-label": "Close"
123
- });
124
- close.addEventListener("click", () => this.handlers.onClose());
125
- header.appendChild(close);
126
- this.body = this.el("div", { class: "arkyc-body" });
127
- this.footer = this.el("div", { class: "arkyc-footer" });
128
- card.appendChild(header);
129
- card.appendChild(this.body);
130
- card.appendChild(this.footer);
131
- this.root.appendChild(card);
125
+ "aria-label": "Close",
126
+ on: { click: () => this.handlers.onClose() }
127
+ })]);
128
+ this.root.appendChild(this.dom.h("div", { class: "arkyc-card" }, [
129
+ header,
130
+ this.body,
131
+ this.footer
132
+ ]));
132
133
  }
133
134
  /**
134
135
  * Populate the header brand group from the current theme: the project logo
@@ -137,16 +138,14 @@ var WidgetView = class {
137
138
  fillBrand() {
138
139
  this.clear(this.brandEl);
139
140
  const lastTenMins = Date.now() - Math.floor(Math.random() * (600 * 1e3));
140
- if (this.theme.showBranding && (this.theme.logoUrl || this.theme.name)) {
141
- if (this.theme.logoUrl) this.brandEl.appendChild(this.el("img", {
142
- class: "arkyc-logo",
143
- src: this.theme.logoUrl + "?stamp=" + lastTenMins
144
- }));
145
- if (this.theme.name) this.brandEl.appendChild(this.el("span", {
146
- class: "arkyc-brand-name",
147
- text: this.theme.name
148
- }));
149
- } else this.brandEl.appendChild(this.el("p", {
141
+ const branded = this.theme.showBranding && (this.theme.logoUrl || this.theme.name);
142
+ this.dom.append(this.brandEl, branded ? [this.theme.logoUrl && this.dom.h("img", {
143
+ class: "arkyc-logo",
144
+ src: `${this.theme.logoUrl}?stamp=${lastTenMins}`
145
+ }), this.theme.name && this.dom.h("span", {
146
+ class: "arkyc-brand-name",
147
+ text: this.theme.name
148
+ })] : this.dom.h("p", {
150
149
  class: "arkyc-title",
151
150
  text: "Verify your identity"
152
151
  }));
@@ -202,6 +201,8 @@ var WidgetView = class {
202
201
  case "selfie_capture": return this.renderCapture("Take a selfie", "user", state.allowSkip, true, state.strictCapture);
203
202
  case "active_liveness": return this.renderActiveLiveness(state.livenessChallenges ?? [], state.allowSkip, state.requireLiveCamera);
204
203
  case "ocr_processing": return this.renderProcessing("Reading your document…");
204
+ case "address_entry": return this.renderAddressForm(state.addressMethods ?? []);
205
+ case "address_processing": return this.renderProcessing("Verifying your address…");
205
206
  case "passive_liveness": return this.renderProcessing("Checking liveness…");
206
207
  case "face_match": return this.renderProcessing("Matching your face…");
207
208
  case "processing": return this.renderProcessing(state.statusLabel ?? "Finalising verification…");
@@ -214,23 +215,21 @@ var WidgetView = class {
214
215
  /** Prepend a decorative illustration for the given screen, when one exists. */
215
216
  appendArt(key) {
216
217
  const art = STEP_ART[key];
217
- if (art) this.body.appendChild(this.el("div", {
218
+ if (art) this.body.appendChild(this.dom.h("div", {
218
219
  class: "arkyc-illus",
219
220
  html: art
220
221
  }));
221
222
  }
222
223
  renderWelcome(handoffAvailable) {
223
224
  this.appendArt("welcome");
224
- this.body.appendChild(this.el("h2", {
225
+ this.dom.append(this.body, [this.dom.h("h2", {
225
226
  class: "arkyc-h",
226
227
  text: "Verify your identity"
227
- }));
228
- this.body.appendChild(this.el("p", {
228
+ }), this.dom.h("p", {
229
229
  class: "arkyc-p",
230
230
  text: "You will need a government-issued ID and a moment to take a selfie. Your data is processed securely."
231
- }));
232
- this.footer.appendChild(this.button("Get started", () => this.handlers.onStart()));
233
- if (handoffAvailable) this.footer.appendChild(this.button("Continue on your phone", () => this.handlers.onUsePhone(), "arkyc-btn-ghost"));
231
+ })]);
232
+ this.dom.append(this.footer, [this.button("Get started", () => this.handlers.onStart()), handoffAvailable && this.button("Continue on your phone", () => this.handlers.onUsePhone(), "arkyc-btn-ghost")]);
234
233
  }
235
234
  /** A neutral connecting screen shown while the widget bootstraps the session. */
236
235
  renderLoading(label = "Loading…") {
@@ -251,14 +250,13 @@ var WidgetView = class {
251
250
  this.clear(this.footer);
252
251
  this.root.classList.remove("arkyc-handoff");
253
252
  this.appendArt(step);
254
- this.body.appendChild(this.el("h2", {
253
+ this.dom.append(this.body, [this.dom.h("h2", {
255
254
  class: "arkyc-h",
256
255
  text: title
257
- }));
258
- this.body.appendChild(this.el("p", {
256
+ }), this.dom.h("p", {
259
257
  class: "arkyc-p",
260
258
  text: body
261
- }));
259
+ })]);
262
260
  this.footer.appendChild(this.button(cta, onContinue));
263
261
  }
264
262
  /**
@@ -271,85 +269,76 @@ var WidgetView = class {
271
269
  this.clear(this.body);
272
270
  this.clear(this.footer);
273
271
  this.root.classList.add("arkyc-handoff");
274
- this.body.appendChild(this.el("h2", {
275
- class: "arkyc-h",
276
- text: "Continue on your phone"
277
- }));
278
- this.body.appendChild(this.el("p", {
279
- class: "arkyc-p",
280
- text: "Scan this code with your phone camera to finish verifying there."
281
- }));
282
- this.body.appendChild(this.el("div", {
283
- class: "arkyc-qr",
284
- html: qrSvg
285
- }));
286
- const waiting = this.el("div", { class: "arkyc-handoff-wait" });
287
- waiting.appendChild(this.el("div", { class: "arkyc-spinner sm" }));
288
- waiting.appendChild(this.el("span", { text: "Waiting for your phone…" }));
289
- this.body.appendChild(waiting);
272
+ this.dom.append(this.body, [
273
+ this.dom.h("h2", {
274
+ class: "arkyc-h",
275
+ text: "Continue on your phone"
276
+ }),
277
+ this.dom.h("p", {
278
+ class: "arkyc-p",
279
+ text: "Scan this code with your phone camera to finish verifying there."
280
+ }),
281
+ this.dom.h("div", {
282
+ class: "arkyc-qr",
283
+ html: qrSvg
284
+ }),
285
+ this.dom.h("div", { class: "arkyc-handoff-wait" }, [this.dom.h("div", { class: "arkyc-spinner sm" }), this.dom.h("span", { text: "Waiting for your phone…" })])
286
+ ]);
290
287
  if (allowContinueHere) this.footer.appendChild(this.button("Continue on this device", () => this.handlers.onContinueHere(), "arkyc-btn-ghost"));
291
288
  }
292
289
  renderDocumentSelection() {
293
- this.body.appendChild(this.el("h2", {
290
+ this.body.appendChild(this.dom.h("h2", {
294
291
  class: "arkyc-h",
295
292
  text: "Select your document"
296
293
  }));
297
- const country = this.el("select", {
294
+ const country = this.dom.field("Country", {
298
295
  class: "arkyc-btn arkyc-btn-ghost arkyc-text-center",
299
296
  name: "country",
300
297
  autocomplete: "country",
301
- "aria-label": "Country"
298
+ choices: countries.map(({ name, iso2, flag }) => ({
299
+ value: iso2,
300
+ label: `${flag} ${name}`
301
+ }))
302
302
  });
303
- const placeholder = this.el("option", {
304
- value: "",
305
- selected: "selected"
306
- });
307
- placeholder.textContent = "Country";
308
- country.appendChild(placeholder);
309
- countries.forEach(({ name, iso2, flag }) => {
310
- const option = this.el("option", { value: iso2 });
311
- option.textContent = `${flag} ${name}`;
312
- country.appendChild(option);
313
- });
314
- const choices = this.el("div", { class: "arkyc-choices" });
315
- choices.appendChild(country);
316
- Object.keys(DOCUMENT_LABELS).forEach((type) => {
303
+ const buttons = Object.keys(DOCUMENT_LABELS).map((type) => {
317
304
  const btn = this.button(DOCUMENT_LABELS[type], () => this.handlers.onDocumentSelected(type, (country.value || "").trim().toUpperCase()));
318
305
  btn.classList.add("arkyc-btn-ghost");
319
- choices.appendChild(btn);
306
+ return btn;
320
307
  });
321
- this.body.appendChild(choices);
308
+ this.body.appendChild(this.dom.h("div", { class: "arkyc-choices" }, [country, ...buttons]));
322
309
  }
323
310
  renderCapture(title, facing, allowSkip, selfie = false, strict = false) {
324
311
  const strictDoc = strict && !selfie;
325
- this.body.appendChild(this.el("h2", {
326
- class: "arkyc-h",
327
- text: title
328
- }));
329
- this.body.appendChild(this.el("p", {
330
- class: "arkyc-p",
331
- text: "Position it clearly in frame, then capture."
332
- }));
333
- const fileInput = this.el("input", {
312
+ const fileInput = this.dom.h("input", {
334
313
  type: "file",
335
314
  accept: "image/*",
336
- class: "arkyc-hidden"
315
+ class: "arkyc-hidden",
316
+ on: { change: () => this.handlers.onImage(Camera.fileFromInput(fileInput)) }
337
317
  });
338
- fileInput.addEventListener("change", () => this.handlers.onImage(Camera.fileFromInput(fileInput)));
339
- this.body.appendChild(fileInput);
318
+ this.dom.append(this.body, [
319
+ this.dom.h("h2", {
320
+ class: "arkyc-h",
321
+ text: title
322
+ }),
323
+ this.dom.h("p", {
324
+ class: "arkyc-p",
325
+ text: "Position it clearly in frame, then capture."
326
+ }),
327
+ fileInput
328
+ ]);
340
329
  if (this.camera.supported) {
341
- const video = this.el("video", { class: `arkyc-preview${selfie ? " selfie" : ""}` });
330
+ const video = this.dom.h("video", { class: `arkyc-preview${selfie ? " selfie" : ""}` });
342
331
  const face = selfie ? this.buildFaceStage(video) : null;
343
332
  const doc = selfie ? null : this.buildDocStage(video);
344
333
  const mount = face?.stage ?? doc.stage;
345
334
  this.body.appendChild(mount);
346
- const hint = this.el("p", { class: "arkyc-p arkyc-hint" });
335
+ const hint = this.dom.h("p", { class: "arkyc-p arkyc-hint" });
347
336
  this.body.appendChild(hint);
348
337
  const confirmCapture = (blob) => {
349
338
  this.destroy();
350
339
  if (blob) {
351
340
  const url = URL.createObjectURL(blob);
352
- const still = this.el("img", {
341
+ const still = this.dom.h("img", {
353
342
  class: `arkyc-preview${selfie ? " selfie" : ""}`,
354
343
  src: url
355
344
  });
@@ -382,16 +371,14 @@ var WidgetView = class {
382
371
  this.runDocumentAutoCapture(video, hint, doc, strictDoc, onCapture);
383
372
  if (!strictDoc) this.footer.appendChild(this.button("Capture", onCapture));
384
373
  }
385
- } else if (strictDoc) {
386
- this.body.appendChild(this.el("div", {
387
- class: "arkyc-badge err",
388
- html: "!"
389
- }));
390
- this.body.appendChild(this.el("p", {
391
- class: "arkyc-p",
392
- text: "This step needs a working camera to scan your document. Please retry on a device with a camera."
393
- }));
394
- } else {
374
+ } else if (strictDoc) this.dom.append(this.body, [this.dom.h("div", {
375
+ class: "arkyc-badge err",
376
+ html: "!"
377
+ }), this.dom.h("p", {
378
+ class: "arkyc-p",
379
+ text: "This step needs a working camera to scan your document. Please retry on a device with a camera."
380
+ })]);
381
+ else {
395
382
  const upload = this.button("Upload photo", () => fileInput.click());
396
383
  this.footer.appendChild(upload);
397
384
  }
@@ -585,18 +572,17 @@ var WidgetView = class {
585
572
  * @returns
586
573
  */
587
574
  renderActiveLiveness(challenges, allowSkip, requireLiveCamera) {
588
- this.body.appendChild(this.el("h2", {
589
- class: "arkyc-h",
590
- text: "Liveness check"
591
- }));
592
- const prompt = this.el("p", {
575
+ const prompt = this.dom.h("p", {
593
576
  class: "arkyc-p",
594
577
  text: "Follow the on-screen prompts. Keep your face centred in the circle."
595
578
  });
596
- this.body.appendChild(prompt);
579
+ this.dom.append(this.body, [this.dom.h("h2", {
580
+ class: "arkyc-h",
581
+ text: "Liveness check"
582
+ }), prompt]);
597
583
  if (!this.camera.supported || !this.camera.canRecord) {
598
584
  if (requireLiveCamera) {
599
- this.body.appendChild(this.el("div", {
585
+ this.body.appendChild(this.dom.h("div", {
600
586
  class: "arkyc-badge err",
601
587
  html: "!"
602
588
  }));
@@ -617,11 +603,10 @@ var WidgetView = class {
617
603
  }
618
604
  return;
619
605
  }
620
- const video = this.el("video", { class: "arkyc-preview selfie" });
606
+ const video = this.dom.h("video", { class: "arkyc-preview selfie" });
621
607
  const face = this.buildFaceStage(video);
622
608
  const dots = this.buildDots(challenges.length);
623
- if (challenges.length > 1) this.body.appendChild(dots.el);
624
- this.body.appendChild(face.stage);
609
+ this.dom.append(this.body, [challenges.length > 1 && dots.el, face.stage]);
625
610
  let recording = null;
626
611
  let index = 0;
627
612
  const performed = [];
@@ -732,20 +717,23 @@ var WidgetView = class {
732
717
  * @returns
733
718
  */
734
719
  buildFaceStage(video) {
735
- const stage = this.el("div", { class: "arkyc-stage" });
736
- stage.setAttribute("data-state", "wait");
737
- stage.appendChild(video);
738
- const ring = this.el("div", {
720
+ const ring = this.dom.h("div", {
739
721
  class: "arkyc-ring",
740
722
  html: "<svg viewBox=\"0 0 100 100\"><circle class=\"arkyc-ring-track\" cx=\"50\" cy=\"50\" r=\"46\"/><circle class=\"arkyc-ring-arc\" cx=\"50\" cy=\"50\" r=\"46\"/></svg>"
741
723
  });
742
- stage.appendChild(ring);
743
- const cue = this.el("div", { class: "arkyc-cue" });
744
- stage.appendChild(cue);
745
- stage.appendChild(this.el("div", {
746
- class: "arkyc-check",
747
- html: CHECK_ICON
748
- }));
724
+ const cue = this.dom.h("div", { class: "arkyc-cue" });
725
+ const stage = this.dom.h("div", {
726
+ class: "arkyc-stage",
727
+ "data-state": "wait"
728
+ }, [
729
+ video,
730
+ ring,
731
+ cue,
732
+ this.dom.h("div", {
733
+ class: "arkyc-check",
734
+ html: CHECK_ICON
735
+ })
736
+ ]);
749
737
  const arc = ring.querySelector(".arkyc-ring-arc");
750
738
  const circumference = 2 * Math.PI * 46;
751
739
  if (arc) {
@@ -772,15 +760,9 @@ var WidgetView = class {
772
760
  * @param doc
773
761
  */
774
762
  buildDots(count) {
775
- const el = this.el("div", { class: "arkyc-dots" });
776
- const dots = [];
777
- for (let i = 0; i < count; i += 1) {
778
- const dot = this.el("div", { class: "arkyc-dot" });
779
- dots.push(dot);
780
- el.appendChild(dot);
781
- }
763
+ const dots = Array.from({ length: count }, () => this.dom.h("div", { class: "arkyc-dot" }));
782
764
  return {
783
- el,
765
+ el: this.dom.h("div", { class: "arkyc-dots" }, dots),
784
766
  setActive: (index) => dots.forEach((d, i) => d.classList.toggle("active", i === index && !d.classList.contains("done"))),
785
767
  markDone: (index) => {
786
768
  const d = dots[index];
@@ -801,18 +783,16 @@ var WidgetView = class {
801
783
  * @returns
802
784
  */
803
785
  buildDocStage(video) {
804
- const stage = this.el("div", { class: "arkyc-doc" });
805
- stage.setAttribute("data-q", "wait");
806
- stage.appendChild(video);
807
- const frame = this.el("div", { class: "arkyc-doc-frame" });
808
- for (const corner of [
786
+ const frame = this.dom.h("div", { class: "arkyc-doc-frame" }, [...[
809
787
  "tl",
810
788
  "tr",
811
789
  "bl",
812
790
  "br"
813
- ]) frame.appendChild(this.el("span", { class: `arkyc-corner ${corner}` }));
814
- frame.appendChild(this.el("div", { class: "arkyc-scan" }));
815
- stage.appendChild(frame);
791
+ ].map((corner) => this.dom.h("span", { class: `arkyc-corner ${corner}` })), this.dom.h("div", { class: "arkyc-scan" })]);
792
+ const stage = this.dom.h("div", {
793
+ class: "arkyc-doc",
794
+ "data-q": "wait"
795
+ }, [video, frame]);
816
796
  return {
817
797
  stage,
818
798
  setQuality: (quality) => stage.setAttribute("data-q", quality),
@@ -820,40 +800,42 @@ var WidgetView = class {
820
800
  };
821
801
  }
822
802
  renderProcessing(label) {
823
- this.body.appendChild(this.el("div", { class: "arkyc-spinner" }));
824
- this.body.appendChild(this.el("p", {
803
+ this.dom.append(this.body, [this.dom.h("div", { class: "arkyc-spinner" }), this.dom.h("p", {
825
804
  class: "arkyc-p",
826
805
  text: label
827
- }));
806
+ })]);
828
807
  }
829
808
  renderResult(decision, errorMessage, opts = {}) {
830
- if (errorMessage) {
831
- this.body.appendChild(this.el("div", {
809
+ if (errorMessage) this.dom.append(this.body, [
810
+ this.dom.h("div", {
832
811
  class: "arkyc-badge err",
833
812
  html: "!"
834
- }));
835
- this.body.appendChild(this.el("h2", {
813
+ }),
814
+ this.dom.h("h2", {
836
815
  class: "arkyc-h",
837
816
  text: "Something went wrong"
838
- }));
839
- this.body.appendChild(this.el("p", {
817
+ }),
818
+ this.dom.h("p", {
840
819
  class: "arkyc-p",
841
820
  text: errorMessage
842
- }));
843
- } else if (opts.terminal) {
821
+ })
822
+ ]);
823
+ else if (opts.terminal) {
844
824
  const n = TERMINAL_NOTICE[opts.status ?? "expired"] ?? EXPIRED_NOTICE;
845
- this.body.appendChild(this.el("div", {
846
- class: `arkyc-badge ${n.cls}`,
847
- text: n.icon
848
- }));
849
- this.body.appendChild(this.el("h2", {
850
- class: "arkyc-h",
851
- text: n.title
852
- }));
853
- this.body.appendChild(this.el("p", {
854
- class: "arkyc-p",
855
- text: n.copy
856
- }));
825
+ this.dom.append(this.body, [
826
+ this.dom.h("div", {
827
+ class: `arkyc-badge ${n.cls}`,
828
+ text: n.icon
829
+ }),
830
+ this.dom.h("h2", {
831
+ class: "arkyc-h",
832
+ text: n.title
833
+ }),
834
+ this.dom.h("p", {
835
+ class: "arkyc-p",
836
+ text: n.copy
837
+ })
838
+ ]);
857
839
  this.footer.appendChild(this.button("Close", () => this.handlers.onClose()));
858
840
  return;
859
841
  } else {
@@ -878,24 +860,128 @@ var WidgetView = class {
878
860
  }
879
861
  };
880
862
  const r = map[decision ?? "requires_review"] ?? map.requires_review;
881
- this.body.appendChild(this.el("div", {
882
- class: `arkyc-badge ${r.cls}`,
883
- text: r.icon
884
- }));
885
- this.body.appendChild(this.el("h2", {
886
- class: "arkyc-h",
887
- text: r.title
888
- }));
889
- this.body.appendChild(this.el("p", {
890
- class: "arkyc-p",
891
- text: r.copy
892
- }));
863
+ this.dom.append(this.body, [
864
+ this.dom.h("div", {
865
+ class: `arkyc-badge ${r.cls}`,
866
+ text: r.icon
867
+ }),
868
+ this.dom.h("h2", {
869
+ class: "arkyc-h",
870
+ text: r.title
871
+ }),
872
+ this.dom.h("p", {
873
+ class: "arkyc-p",
874
+ text: r.copy
875
+ })
876
+ ]);
893
877
  }
894
878
  this.footer.appendChild(this.button("Done", () => this.handlers.onAcknowledge()));
895
879
  }
880
+ /**
881
+ * The address-entry form (opt-in address stage). Inputs shown depend on methods.
882
+ *
883
+ * @param methods
884
+ */
885
+ renderAddressForm(methods) {
886
+ this.body.appendChild(this.dom.h("h2", {
887
+ class: "arkyc-h",
888
+ text: "Your address"
889
+ }));
890
+ this.body.appendChild(this.dom.h("p", {
891
+ class: "arkyc-p",
892
+ text: "Enter your residential address so we can verify it."
893
+ }));
894
+ const line1 = this.dom.field("Address line 1", {
895
+ autocomplete: "address-line1",
896
+ name: "street"
897
+ });
898
+ const line2 = this.dom.field("Address line 2 (optional)", {
899
+ autocomplete: "address-line2",
900
+ name: "town"
901
+ });
902
+ const city = this.dom.field("City", {
903
+ autocomplete: "address-level2",
904
+ name: "city"
905
+ });
906
+ const region = this.dom.field("State / region", {
907
+ autocomplete: "address-level1",
908
+ name: "state"
909
+ });
910
+ const postal = this.dom.field("ZIP/Postal code", {
911
+ autocomplete: "postal-code",
912
+ name: "zip"
913
+ });
914
+ const country = this.dom.field("Country", {
915
+ name: "country_iso2",
916
+ choices: countries.map((e) => ({
917
+ value: e.iso2,
918
+ label: `${e.flag} ${e.name}`
919
+ }))
920
+ });
921
+ this.body.appendChild(this.dom.h("div", { class: "arkyc-addr-form" }, [
922
+ line1,
923
+ line2,
924
+ city,
925
+ region,
926
+ postal,
927
+ country
928
+ ]));
929
+ let poa = null;
930
+ if (methods.includes("poa_document")) {
931
+ const file = this.dom.h("input", {
932
+ class: "arkyc-addr-input",
933
+ type: "file",
934
+ accept: "image/*",
935
+ on: { change: () => poa = file.files?.[0] ?? null }
936
+ });
937
+ this.dom.append(this.body, [this.dom.h("p", {
938
+ class: "arkyc-p",
939
+ text: "Upload a proof of address (utility bill or bank statement)."
940
+ }), file]);
941
+ }
942
+ let useLocation = false;
943
+ const requiresLocation = methods.includes("device_location");
944
+ const submit = this.button("Continue", () => this.handlers.onAddress({
945
+ line1: line1.value.trim() || null,
946
+ line2: line2.value.trim() || null,
947
+ city: city.value.trim() || null,
948
+ region: region.value.trim() || null,
949
+ postalCode: postal.value.trim() || null,
950
+ country: country.value.trim().toUpperCase() || null,
951
+ poa,
952
+ useLocation
953
+ }));
954
+ if (requiresLocation) {
955
+ const status = this.dom.h("p", { class: "arkyc-p" });
956
+ const cb = this.dom.h("input", {
957
+ type: "checkbox",
958
+ on: { change: () => {
959
+ if (!cb.checked) {
960
+ useLocation = false;
961
+ status.textContent = "";
962
+ submit.setAttribute("disabled", "true");
963
+ return;
964
+ }
965
+ status.textContent = "Requesting your location…";
966
+ submit.setAttribute("disabled", "true");
967
+ Promise.resolve(this.handlers.onShareLocation()).then((ok) => {
968
+ useLocation = ok;
969
+ cb.checked = ok;
970
+ if (ok) submit.removeAttribute("disabled");
971
+ else submit.setAttribute("disabled", "true");
972
+ status.textContent = ok ? "Location shared ✓" : "Couldn’t access your location, allow location access to continue.";
973
+ });
974
+ } }
975
+ });
976
+ submit.setAttribute("disabled", "true");
977
+ this.body.appendChild(this.dom.h("label", { class: "arkyc-addr-opt" }, [cb, this.dom.h("span", { text: "Allow access to my current location to confirm my address" })]));
978
+ this.body.appendChild(status);
979
+ }
980
+ this.footer.appendChild(submit);
981
+ }
896
982
  button(label, onClick, extraClass) {
897
983
  const cls = extraClass ? `arkyc-btn ${extraClass}` : "arkyc-btn";
898
- const btn = this.el("button", {
984
+ const btn = this.dom.h("button", {
899
985
  class: cls,
900
986
  text: label
901
987
  });
@@ -912,18 +998,6 @@ var WidgetView = class {
912
998
  clear(node) {
913
999
  while (node.firstChild) node.removeChild(node.firstChild);
914
1000
  }
915
- el(tag, props = {}) {
916
- const node = this.doc.createElement(tag);
917
- for (const [key, value] of Object.entries(props)) {
918
- if (value == null) continue;
919
- if (key === "class") node.className = value;
920
- else if (key === "text") node.textContent = value;
921
- else if (key === "html") node.innerHTML = value;
922
- else if (key === "value" || key === "src" || key === "type" || key === "accept" || key === "placeholder") node[key] = value;
923
- else node.setAttribute(key, value);
924
- }
925
- return node;
926
- }
927
1001
  };
928
1002
  //#endregion
929
1003
  export { WidgetView };