Put the otp input after its boxes
A seventh box appeared when the field took focus. daisyUI positions each otp box with nth-child, which counts *every* child of the container, not just the spans. With the input as the first child, all six boxes shifted one stride right and the container matched :has(>span:nth-child(7)), so it grew to seven strides wide. The phantom box was the ::after active-box marker -- transparent until :focus-within gives it an outline -- sitting in the empty stride that the off-by-one had opened up. The input goes last, which is also how daisyUI's own examples order it. A test counts the boxes ahead of the input so this cannot come back. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -265,6 +265,15 @@ class TwoFactorPageTests(TestCase):
|
|||||||
self.assertContains(self.response, 'name="code"')
|
self.assertContains(self.response, 'name="code"')
|
||||||
self.assertContains(self.response, "otp otp-lg")
|
self.assertContains(self.response, "otp otp-lg")
|
||||||
|
|
||||||
|
def test_the_input_comes_after_the_boxes(self):
|
||||||
|
# daisyUI places each box with nth-child, which counts every child. With the input
|
||||||
|
# first, all six boxes shift a stride right, the container grows to seven strides
|
||||||
|
# and the ::after focus marker appears as a phantom seventh box.
|
||||||
|
html = self.response.content.decode()
|
||||||
|
otp = html[html.index('class="otp otp-lg"') : html.index('name="code"')]
|
||||||
|
|
||||||
|
self.assertEqual(otp.count("<span></span>"), 6)
|
||||||
|
|
||||||
def test_the_otp_field_has_no_placeholder(self):
|
def test_the_otp_field_has_no_placeholder(self):
|
||||||
# allauth sets placeholder="Code"; inside the boxes it reads as a typed-in code.
|
# allauth sets placeholder="Code"; inside the boxes it reads as a typed-in code.
|
||||||
self.assertNotContains(self.response, 'placeholder="Code"')
|
self.assertNotContains(self.response, 'placeholder="Code"')
|
||||||
|
|||||||
@@ -43,20 +43,26 @@
|
|||||||
{% with icon=field|field_icon %}
|
{% with icon=field|field_icon %}
|
||||||
{% if field|is_otp %}
|
{% if field|is_otp %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
daisyUI's otp is one input overlaid on the boxes, and it sizes itself from
|
daisyUI's otp is one input overlaid on the boxes. Child order is load-bearing:
|
||||||
its `span` children — so the sr-only label goes OUTSIDE the container, or it
|
it places each box with `nth-child`, which counts *every* child, not just the
|
||||||
would count as a seventh box and throw the width off.
|
spans — so the input must come LAST. Put it first and all six boxes shift one
|
||||||
|
stride right, the container matches :has(>span:nth-child(7)) and grows to
|
||||||
|
seven strides, and the ::after active-box marker (invisible until focus) shows
|
||||||
|
up in the gap as a phantom seventh box.
|
||||||
|
|
||||||
|
For the same reason the sr-only label lives outside the container: another
|
||||||
|
child would throw the count off again.
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
{% if attrs.unlabeled %}<label class="sr-only" for="{{ field.id_for_label }}">{{ field.label }}</label>{% endif %}
|
{% if attrs.unlabeled %}<label class="sr-only" for="{{ field.id_for_label }}">{{ field.label }}</label>{% endif %}
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<div class="otp otp-lg" data-otp>
|
<div class="otp otp-lg" data-otp>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
{{ field|as_otp }}
|
{{ field|as_otp }}
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% elif icon %}
|
{% elif icon %}
|
||||||
|
|||||||
Reference in New Issue
Block a user