diff --git a/.changeset/strength-precedes-input.md b/.changeset/strength-precedes-input.md new file mode 100644 index 000000000..59ae8a0f4 --- /dev/null +++ b/.changeset/strength-precedes-input.md @@ -0,0 +1,5 @@ +--- +"@tabler/core": patch +--- + +Fixed the strength meter rating a confirm-password field after it instead of the password field before it. diff --git a/core/js/src/strength.ts b/core/js/src/strength.ts index 3c35baa71..09527364b 100644 --- a/core/js/src/strength.ts +++ b/core/js/src/strength.ts @@ -170,11 +170,14 @@ class Strength extends BaseComponent { return SelectorEngine.findOne(input) as HTMLInputElement | null } - // Without an explicit selector only the meter's own parent is searched, so - // a form with several password fields cannot bind the wrong one. + // Without an explicit selector only the meter's own parent is searched. + // The nearest field before the meter wins, so a confirm-password field + // placed after it is never mistaken for the one being rated; a meter + // placed above its field falls back to the first field after it. const parent = this._element.parentElement const fields = parent ? (SelectorEngine.find(SELECTOR_PASSWORD, parent) as HTMLInputElement[]) : [] - return fields[fields.length - 1] ?? null + const precedingFields = fields.filter((field) => Boolean(field.compareDocumentPosition(this._element) & Node.DOCUMENT_POSITION_FOLLOWING)) + return precedingFields[precedingFields.length - 1] ?? fields[0] ?? null } _getText(): HTMLElement | null { diff --git a/core/js/tests/unit/strength.spec.ts b/core/js/tests/unit/strength.spec.ts index 8380d68f9..c5014f56c 100644 --- a/core/js/tests/unit/strength.spec.ts +++ b/core/js/tests/unit/strength.spec.ts @@ -69,6 +69,45 @@ describe('Strength', () => { expect(instance._input).toBe(fixtureEl.querySelector('#remote')) }) + + it('should bind the password field before it, not a confirm field after it', () => { + fixtureEl.innerHTML = ` +
+ +
+ +
` + + const instance = new Strength(meter()) + + expect(instance._input).toBe(fixtureEl.querySelector('#pw')) + }) + + it('should bind the last of several preceding fields, ignoring one after it', () => { + fixtureEl.innerHTML = ` +
+ + +
+ +
` + + const instance = new Strength(meter()) + + expect(instance._input).toBe(fixtureEl.querySelector('#new')) + }) + + it('should bind the field after it when none precedes it', () => { + fixtureEl.innerHTML = ` +
+
+ +
` + + const instance = new Strength(meter()) + + expect(instance._input).toBe(fixtureEl.querySelector('#confirm')) + }) }) describe('levels', () => {