|
297 | 297 | } |
298 | 298 |
|
299 | 299 | class CapWidget extends HTMLElement { |
| 300 | + static formAssociated = true; |
300 | 301 | #resetTimer = null; |
301 | 302 | #workersCount = navigator.hardwareConcurrency || 8; |
302 | 303 | token = null; |
|
305 | 306 | #host; |
306 | 307 | #solving = false; |
307 | 308 | #eventHandlers; |
| 309 | + #internals; |
308 | 310 |
|
309 | 311 | #speculative = null; |
310 | 312 | #speculativeTimer = null; |
|
564 | 566 | "onerror", |
565 | 567 | "data-cap-worker-count", |
566 | 568 | "data-cap-i18n-initial-state", |
| 569 | + "required", |
567 | 570 | ]; |
568 | 571 | } |
569 | 572 |
|
|
580 | 583 | this.boundHandleSolve = this.handleSolve.bind(this); |
581 | 584 | this.boundHandleError = this.handleError.bind(this); |
582 | 585 | this.boundHandleReset = this.handleReset.bind(this); |
| 586 | + |
| 587 | + try { |
| 588 | + this.#internals = this.attachInternals(); |
| 589 | + } catch {} |
| 590 | + } |
| 591 | + |
| 592 | + #updateValidity() { |
| 593 | + if (!this.#internals?.setValidity) return; |
| 594 | + if (this.hasAttribute("required") && !this.token) { |
| 595 | + this.#internals.setValidity( |
| 596 | + { valueMissing: true }, |
| 597 | + this.getI18nText("required-label", "Please verify you're human"), |
| 598 | + this.#div || this, |
| 599 | + ); |
| 600 | + } else { |
| 601 | + this.#internals.setValidity({}); |
| 602 | + } |
583 | 603 | } |
584 | 604 |
|
585 | 605 | initialize() { |
|
624 | 644 | ) { |
625 | 645 | this.animateLabel(this.getI18nText("initial-state", "Verify you're human")); |
626 | 646 | } |
| 647 | + |
| 648 | + if (name === "required") { |
| 649 | + this.#updateValidity(); |
| 650 | + } |
627 | 651 | } |
628 | 652 |
|
629 | 653 | async connectedCallback() { |
|
642 | 666 | this.#host.innerHTML = `<input type="hidden" name="${fieldName}">`; |
643 | 667 |
|
644 | 668 | this.#attachInteractionListeners(); |
| 669 | + this.#updateValidity(); |
| 670 | + |
| 671 | + this.addEventListener("invalid", this.#handleInvalid); |
645 | 672 | } |
646 | 673 |
|
| 674 | + #handleInvalid = () => { |
| 675 | + if (!this.#div) return; |
| 676 | + try { |
| 677 | + this.scrollIntoView({ behavior: "smooth", block: "center" }); |
| 678 | + } catch { |
| 679 | + this.scrollIntoView(); |
| 680 | + } |
| 681 | + this.#div.classList.remove("invalid"); |
| 682 | + void this.#div.offsetWidth; |
| 683 | + this.#div.classList.add("invalid"); |
| 684 | + setTimeout(() => this.#div?.classList.remove("invalid"), 1500); |
| 685 | + }; |
| 686 | + |
647 | 687 | async solve() { |
648 | 688 | if (this.#solving) { |
649 | 689 | return; |
|
1142 | 1182 | handleSolve(event) { |
1143 | 1183 | this.updateUI("done", this.getI18nText("solved-label", "You're a human"), true); |
1144 | 1184 | this.executeAttributeCode("onsolve", event); |
| 1185 | + this.#internals?.setValidity?.({}); |
| 1186 | + this.#div?.classList.remove("invalid"); |
1145 | 1187 | } |
1146 | 1188 |
|
1147 | 1189 | handleError(event) { |
|
1154 | 1196 | handleReset(event) { |
1155 | 1197 | this.updateUI("", this.getI18nText("initial-state", "I'm a human")); |
1156 | 1198 | this.executeAttributeCode("onreset", event); |
| 1199 | + this.#updateValidity(); |
1157 | 1200 | } |
1158 | 1201 |
|
1159 | 1202 | executeAttributeCode(attributeName, event) { |
|
1188 | 1231 | clearTimeout(this.#resetTimer); |
1189 | 1232 | this.#resetTimer = null; |
1190 | 1233 | } |
1191 | | - this.dispatchEvent("reset"); |
1192 | 1234 | this.token = null; |
| 1235 | + this.dispatchEvent("reset"); |
1193 | 1236 | const fieldName = this.getAttribute("data-cap-hidden-field-name") || "cap-token"; |
1194 | 1237 | if (this.querySelector(`input[name='${fieldName}']`)) { |
1195 | 1238 | this.querySelector(`input[name='${fieldName}']`).value = ""; |
|
0 commit comments