Fix the hours field's label sitting on the outline
The label read "Horas por semana" struck through by the field's own border: 5px above the wrapper instead of 18, straddling the 2px outline. Angular Material measures an outlined field's floating label once on init and writes the result to the element as an inline transform. This field is the only one inside an @if, so it is measured before it has any geometry and keeps the unfloated offset. @pi/ui already publishes the right values for every other field and its rule matches this one too -- it simply loses to an inline style, so the fix restates the library's own numbers with `!important`, which is what outranks one. It also gains a placeholder, which every other field already had. The e2e config now derives the dev server's port from BASE_URL instead of pinning 4200. With `reuseExistingServer`, an unrelated project's dev server on that port gets adopted silently and all five tests fail with "element(s) not found" while the page under test was never served -- which is exactly what happened while verifying this. Verified: all four labels now land at -18 with a 48px box, light and dark; lint, 15 unit tests, build and 5 e2e green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,19 @@ const here = dirname(fileURLToPath(import.meta.url));
|
|||||||
const workspaceRoot = resolve(here, '..');
|
const workspaceRoot = resolve(here, '..');
|
||||||
const baseURL = process.env['BASE_URL'] || 'http://localhost:4200';
|
const baseURL = process.env['BASE_URL'] || 'http://localhost:4200';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The dev server is started on whatever port `BASE_URL` names, rather than
|
||||||
|
* always 4200.
|
||||||
|
*
|
||||||
|
* `reuseExistingServer` means Playwright will happily adopt whatever is already
|
||||||
|
* listening there — and if that is a different project's dev server, every test
|
||||||
|
* fails with "element(s) not found" while the real cause is that the page under
|
||||||
|
* test was never served. Set BASE_URL to move both halves at once:
|
||||||
|
*
|
||||||
|
* BASE_URL=http://localhost:4280 npx nx e2e landing-e2e
|
||||||
|
*/
|
||||||
|
const port = new URL(baseURL).port || '4200';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
testDir: './src',
|
testDir: './src',
|
||||||
outputDir: resolve(workspaceRoot, 'dist/.playwright/landing-e2e/test-output'),
|
outputDir: resolve(workspaceRoot, 'dist/.playwright/landing-e2e/test-output'),
|
||||||
@@ -25,7 +38,7 @@ export default defineConfig({
|
|||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
},
|
},
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'npx nx run landing:serve',
|
command: `npx nx run landing:serve --port=${port}`,
|
||||||
url: baseURL,
|
url: baseURL,
|
||||||
reuseExistingServer: true,
|
reuseExistingServer: true,
|
||||||
timeout: 180_000,
|
timeout: 180_000,
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ export interface Copy {
|
|||||||
email: string;
|
email: string;
|
||||||
organisation: string;
|
organisation: string;
|
||||||
hours: string;
|
hours: string;
|
||||||
|
hoursHint: string;
|
||||||
message: string;
|
message: string;
|
||||||
messageHint: string;
|
messageHint: string;
|
||||||
consent: string;
|
consent: string;
|
||||||
@@ -167,6 +168,7 @@ const es: Copy = {
|
|||||||
email: 'Correo',
|
email: 'Correo',
|
||||||
organisation: 'Organización',
|
organisation: 'Organización',
|
||||||
hours: 'Horas por semana',
|
hours: 'Horas por semana',
|
||||||
|
hoursHint: 'Por ejemplo, 4',
|
||||||
message: 'Mensaje',
|
message: 'Mensaje',
|
||||||
messageHint: 'Mínimo 20 caracteres.',
|
messageHint: 'Mínimo 20 caracteres.',
|
||||||
consent: 'Acepto que guardéis mis datos para responderme.',
|
consent: 'Acepto que guardéis mis datos para responderme.',
|
||||||
@@ -264,6 +266,7 @@ const en: Copy = {
|
|||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
organisation: 'Organisation',
|
organisation: 'Organisation',
|
||||||
hours: 'Hours per week',
|
hours: 'Hours per week',
|
||||||
|
hoursHint: 'For example, 4',
|
||||||
message: 'Message',
|
message: 'Message',
|
||||||
messageHint: 'At least 20 characters.',
|
messageHint: 'At least 20 characters.',
|
||||||
consent: 'I agree to have my details stored so you can reply.',
|
consent: 'I agree to have my details stored so you can reply.',
|
||||||
@@ -361,6 +364,7 @@ const ca: Copy = {
|
|||||||
email: 'Correu',
|
email: 'Correu',
|
||||||
organisation: 'Organització',
|
organisation: 'Organització',
|
||||||
hours: 'Hores per setmana',
|
hours: 'Hores per setmana',
|
||||||
|
hoursHint: 'Per exemple, 4',
|
||||||
message: 'Missatge',
|
message: 'Missatge',
|
||||||
messageHint: 'Mínim 20 caràcters.',
|
messageHint: 'Mínim 20 caràcters.',
|
||||||
consent: 'Accepto que deseu les meves dades per respondre’m.',
|
consent: 'Accepto que deseu les meves dades per respondre’m.',
|
||||||
|
|||||||
@@ -28,3 +28,28 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--pi-color-warn, #d64545);
|
color: var(--pi-color-warn, #d64545);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The hours field sits inside an @if, and that is what breaks its label.
|
||||||
|
//
|
||||||
|
// Angular Material measures an outlined field's floating label once, on init,
|
||||||
|
// and writes the result to the element as an inline
|
||||||
|
// `transform: var(--mat-mdc-form-field-label-transform, translateY(-50%) …)`.
|
||||||
|
// A field created inside a conditional block is measured before it has any
|
||||||
|
// geometry, so the value it keeps is the unfloated one: the label ends up 5px
|
||||||
|
// above the wrapper instead of 18, straddling the 2px outline and struck
|
||||||
|
// through by it.
|
||||||
|
//
|
||||||
|
// `@pi/ui` already publishes the right geometry for every other field
|
||||||
|
// (`translateY(-3rem) scale(1)`, a 48px label box), and that rule matches this
|
||||||
|
// one too — it simply loses to the inline style. `!important` is what beats an
|
||||||
|
// inline declaration, and it is restating the library's own values, not
|
||||||
|
// inventing new ones.
|
||||||
|
:host ::ng-deep .pi-field.pi-form-field__number {
|
||||||
|
.mdc-notched-outline .mdc-floating-label {
|
||||||
|
height: var(--pi-spacing-xxxx-large) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-text-field:not(.mat-form-field-no-animations) .mdc-floating-label {
|
||||||
|
transform: translateY(-3rem) scale(1) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -115,7 +115,17 @@ type Kind = 'contribute' | 'request';
|
|||||||
<pi-label [isRequired]="true">{{ copy().form.organisation }}</pi-label>
|
<pi-label [isRequired]="true">{{ copy().form.organisation }}</pi-label>
|
||||||
</pi-text-field>
|
</pi-text-field>
|
||||||
} @else {
|
} @else {
|
||||||
<pi-number-field [formField]="contact.hours_per_week">
|
<!--
|
||||||
|
The placeholder is not decoration. Without one, Material leaves
|
||||||
|
this label in its unfloated position and writes an inline
|
||||||
|
translateY(-50%) that drops the text straight onto the outline's
|
||||||
|
top border, struck through by it. Every other field here has a
|
||||||
|
placeholder, which is why this was the only one that looked wrong.
|
||||||
|
-->
|
||||||
|
<pi-number-field
|
||||||
|
[formField]="contact.hours_per_week"
|
||||||
|
[placeholder]="copy().form.hoursHint"
|
||||||
|
>
|
||||||
<pi-label>{{ copy().form.hours }}</pi-label>
|
<pi-label>{{ copy().form.hours }}</pi-label>
|
||||||
</pi-number-field>
|
</pi-number-field>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user