@portnet/ui 6.0.1 → 6.0.2
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/CHANGELOG.md +85 -0
- package/dist/esm/index.js +18 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/v2/__tests__/FieldError.test.js +0 -1
- package/dist/esm/v2/__tests__/FieldError.test.js.map +1 -1
- package/dist/esm/v2/__tests__/FieldWidth.test.js +146 -0
- package/dist/esm/v2/__tests__/FieldWidth.test.js.map +1 -0
- package/dist/esm/v2/__tests__/FileCard.test.js +0 -1
- package/dist/esm/v2/__tests__/FileCard.test.js.map +1 -1
- package/dist/esm/v2/__tests__/FormikCheckbox.test.js +265 -0
- package/dist/esm/v2/__tests__/FormikCheckbox.test.js.map +1 -0
- package/dist/esm/v2/__tests__/FormikDateTimePicker.test.js +248 -0
- package/dist/esm/v2/__tests__/FormikDateTimePicker.test.js.map +1 -0
- package/dist/esm/v2/__tests__/FormikRadioGroup.test.js +380 -0
- package/dist/esm/v2/__tests__/FormikRadioGroup.test.js.map +1 -0
- package/dist/esm/v2/__tests__/MobileRowCard.test.js +0 -1
- package/dist/esm/v2/__tests__/MobileRowCard.test.js.map +1 -1
- package/dist/esm/v2/__tests__/SectionErrorBoundary.test.js.map +1 -1
- package/dist/esm/v2/__tests__/labels-merge.test.js +0 -1
- package/dist/esm/v2/__tests__/labels-merge.test.js.map +1 -1
- package/dist/esm/v2/components/DateTimePicker.js +37 -4
- package/dist/esm/v2/components/DateTimePicker.js.map +1 -1
- package/dist/esm/v2/components/Field.js +63 -1
- package/dist/esm/v2/components/Field.js.map +1 -1
- package/dist/esm/v2/components/index.js +27 -0
- package/dist/esm/v2/components/index.js.map +1 -1
- package/dist/esm/v2/index.js +12 -1
- package/dist/esm/v2/index.js.map +1 -1
- package/dist/esm/v2/patterns/FavoriteColumn.js +14 -61
- package/dist/esm/v2/patterns/FavoriteColumn.js.map +1 -1
- package/dist/esm/v2/patterns/FormikCheckbox.js +140 -0
- package/dist/esm/v2/patterns/FormikCheckbox.js.map +1 -0
- package/dist/esm/v2/patterns/FormikCombobox.js +5 -2
- package/dist/esm/v2/patterns/FormikCombobox.js.map +1 -1
- package/dist/esm/v2/patterns/FormikDatePicker.js +13 -47
- package/dist/esm/v2/patterns/FormikDatePicker.js.map +1 -1
- package/dist/esm/v2/patterns/FormikDateTimePicker.js +150 -0
- package/dist/esm/v2/patterns/FormikDateTimePicker.js.map +1 -0
- package/dist/esm/v2/patterns/FormikInput.js +5 -2
- package/dist/esm/v2/patterns/FormikInput.js.map +1 -1
- package/dist/esm/v2/patterns/FormikRadioGroup.js +141 -0
- package/dist/esm/v2/patterns/FormikRadioGroup.js.map +1 -0
- package/dist/esm/v2/patterns/FormikSelect.js +5 -2
- package/dist/esm/v2/patterns/FormikSelect.js.map +1 -1
- package/dist/esm/v2/patterns/FormikTextarea.js +5 -2
- package/dist/esm/v2/patterns/FormikTextarea.js.map +1 -1
- package/dist/esm/v2/patterns/KeyboardShortcutsHelp.js +25 -8
- package/dist/esm/v2/patterns/KeyboardShortcutsHelp.js.map +1 -1
- package/dist/esm/v2/patterns/RowFavoriteToggle.js +122 -0
- package/dist/esm/v2/patterns/RowFavoriteToggle.js.map +1 -0
- package/dist/esm/v2/patterns/_pickerSerialisation.js +74 -0
- package/dist/esm/v2/patterns/_pickerSerialisation.js.map +1 -0
- package/dist/esm/v2/patterns/index.js +83 -0
- package/dist/esm/v2/patterns/index.js.map +1 -1
- package/dist/esm/v2/personalization/capabilities/gridPreferences.js +209 -0
- package/dist/esm/v2/personalization/capabilities/gridPreferences.js.map +1 -0
- package/dist/esm/v2/personalization/capabilities/index.js +5 -0
- package/dist/esm/v2/personalization/capabilities/index.js.map +1 -1
- package/dist/esm/v2/personalization/index.js +18 -0
- package/dist/esm/v2/personalization/index.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/v2/__tests__/FieldError.test.js +26 -30
- package/dist/v2/__tests__/FieldError.test.js.map +1 -1
- package/dist/v2/__tests__/FieldWidth.test.js +150 -0
- package/dist/v2/__tests__/FieldWidth.test.js.map +1 -0
- package/dist/v2/__tests__/FileCard.test.js +25 -29
- package/dist/v2/__tests__/FileCard.test.js.map +1 -1
- package/dist/v2/__tests__/FormikCheckbox.test.js +268 -0
- package/dist/v2/__tests__/FormikCheckbox.test.js.map +1 -0
- package/dist/v2/__tests__/FormikDateTimePicker.test.js +247 -0
- package/dist/v2/__tests__/FormikDateTimePicker.test.js.map +1 -0
- package/dist/v2/__tests__/FormikRadioGroup.test.js +381 -0
- package/dist/v2/__tests__/FormikRadioGroup.test.js.map +1 -0
- package/dist/v2/__tests__/MobileRowCard.test.js +52 -56
- package/dist/v2/__tests__/MobileRowCard.test.js.map +1 -1
- package/dist/v2/__tests__/SectionErrorBoundary.test.js.map +1 -1
- package/dist/v2/__tests__/labels-merge.test.js +20 -24
- package/dist/v2/__tests__/labels-merge.test.js.map +1 -1
- package/dist/v2/components/DateTimePicker.d.ts.map +1 -1
- package/dist/v2/components/DateTimePicker.js +37 -4
- package/dist/v2/components/DateTimePicker.js.map +1 -1
- package/dist/v2/components/Field.d.ts +59 -1
- package/dist/v2/components/Field.d.ts.map +1 -1
- package/dist/v2/components/Field.js +62 -2
- package/dist/v2/components/Field.js.map +1 -1
- package/dist/v2/components/index.d.ts +27 -0
- package/dist/v2/components/index.d.ts.map +1 -1
- package/dist/v2/components/index.js.map +1 -1
- package/dist/v2/index.d.ts.map +1 -1
- package/dist/v2/index.js.map +1 -1
- package/dist/v2/patterns/FavoriteColumn.d.ts +9 -9
- package/dist/v2/patterns/FavoriteColumn.d.ts.map +1 -1
- package/dist/v2/patterns/FavoriteColumn.js +14 -61
- package/dist/v2/patterns/FavoriteColumn.js.map +1 -1
- package/dist/v2/patterns/FormikCheckbox.d.ts +77 -0
- package/dist/v2/patterns/FormikCheckbox.d.ts.map +1 -0
- package/dist/v2/patterns/FormikCheckbox.js +147 -0
- package/dist/v2/patterns/FormikCheckbox.js.map +1 -0
- package/dist/v2/patterns/FormikCombobox.d.ts +7 -1
- package/dist/v2/patterns/FormikCombobox.d.ts.map +1 -1
- package/dist/v2/patterns/FormikCombobox.js +5 -2
- package/dist/v2/patterns/FormikCombobox.js.map +1 -1
- package/dist/v2/patterns/FormikDatePicker.d.ts +13 -1
- package/dist/v2/patterns/FormikDatePicker.d.ts.map +1 -1
- package/dist/v2/patterns/FormikDatePicker.js +33 -66
- package/dist/v2/patterns/FormikDatePicker.js.map +1 -1
- package/dist/v2/patterns/FormikDateTimePicker.d.ts +111 -0
- package/dist/v2/patterns/FormikDateTimePicker.d.ts.map +1 -0
- package/dist/v2/patterns/FormikDateTimePicker.js +158 -0
- package/dist/v2/patterns/FormikDateTimePicker.js.map +1 -0
- package/dist/v2/patterns/FormikInput.d.ts +11 -1
- package/dist/v2/patterns/FormikInput.d.ts.map +1 -1
- package/dist/v2/patterns/FormikInput.js +5 -2
- package/dist/v2/patterns/FormikInput.js.map +1 -1
- package/dist/v2/patterns/FormikRadioGroup.d.ts +80 -0
- package/dist/v2/patterns/FormikRadioGroup.d.ts.map +1 -0
- package/dist/v2/patterns/FormikRadioGroup.js +148 -0
- package/dist/v2/patterns/FormikRadioGroup.js.map +1 -0
- package/dist/v2/patterns/FormikSelect.d.ts +9 -1
- package/dist/v2/patterns/FormikSelect.d.ts.map +1 -1
- package/dist/v2/patterns/FormikSelect.js +5 -2
- package/dist/v2/patterns/FormikSelect.js.map +1 -1
- package/dist/v2/patterns/FormikTextarea.d.ts +9 -1
- package/dist/v2/patterns/FormikTextarea.d.ts.map +1 -1
- package/dist/v2/patterns/FormikTextarea.js +5 -2
- package/dist/v2/patterns/FormikTextarea.js.map +1 -1
- package/dist/v2/patterns/KeyboardShortcutsHelp.d.ts +14 -1
- package/dist/v2/patterns/KeyboardShortcutsHelp.d.ts.map +1 -1
- package/dist/v2/patterns/KeyboardShortcutsHelp.js +25 -8
- package/dist/v2/patterns/KeyboardShortcutsHelp.js.map +1 -1
- package/dist/v2/patterns/RowFavoriteToggle.d.ts +92 -0
- package/dist/v2/patterns/RowFavoriteToggle.d.ts.map +1 -0
- package/dist/v2/patterns/RowFavoriteToggle.js +127 -0
- package/dist/v2/patterns/RowFavoriteToggle.js.map +1 -0
- package/dist/v2/patterns/_pickerSerialisation.d.ts +44 -0
- package/dist/v2/patterns/_pickerSerialisation.d.ts.map +1 -0
- package/dist/v2/patterns/_pickerSerialisation.js +81 -0
- package/dist/v2/patterns/_pickerSerialisation.js.map +1 -0
- package/dist/v2/patterns/index.d.ts +87 -0
- package/dist/v2/patterns/index.d.ts.map +1 -1
- package/dist/v2/patterns/index.js +34 -0
- package/dist/v2/patterns/index.js.map +1 -1
- package/dist/v2/personalization/capabilities/gridPreferences.d.ts +100 -0
- package/dist/v2/personalization/capabilities/gridPreferences.d.ts.map +1 -0
- package/dist/v2/personalization/capabilities/gridPreferences.js +215 -0
- package/dist/v2/personalization/capabilities/gridPreferences.js.map +1 -0
- package/dist/v2/personalization/capabilities/index.d.ts +6 -0
- package/dist/v2/personalization/capabilities/index.d.ts.map +1 -1
- package/dist/v2/personalization/capabilities/index.js +13 -0
- package/dist/v2/personalization/capabilities/index.js.map +1 -1
- package/dist/v2/personalization/index.d.ts +18 -0
- package/dist/v2/personalization/index.d.ts.map +1 -1
- package/dist/v2/personalization/index.js +13 -0
- package/dist/v2/personalization/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MobileRowCard.test.js","names":["React","render","screen","fireEvent","MobileRowCard","jsx","_jsx","describe","test","title","subtitle","meta","expect","getByText","toBeInTheDocument","undefined","queryByText","not","_render","rerender","toHaveAttribute","children","queryByTitle","kv","label","value","getAllByText","toHaveLength","fallback","chips","_render2","actions","queryByRole","divider","onClick","actionsAriaLabel","getByRole","name","onCardClick","jest","fn","leading","click","toHaveBeenCalledTimes","onAction","trailing","toHaveBeenCalled","onCardKeyDown","onKeyDown","keyDown","key","visualiser","kebab","compareDocumentPosition","Node","DOCUMENT_POSITION_FOLLOWING","toBeTruthy"],"sources":["../../../../src/lib/v2/__tests__/MobileRowCard.test.tsx"],"sourcesContent":["import \"@testing-library/jest-dom\";\r\nimport * as React from \"react\";\r\nimport { render, screen, fireEvent } from \"@testing-library/react\";\r\nimport MobileRowCard from \"../components/MobileRowCard\";\r\n\r\n/**\r\n * MobileRowCard — slot contract tests.\r\n *\r\n * WHY THIS FILE EXISTS\r\n * --------------------\r\n * §10.3 R-G.6 MUST — \"Snapshot Storybook + tests Jest/Testing-Library\" — was\r\n * unmet for THREE props added to this component across successive module\r\n * audits: `kv` and `leading` (ms-dfd-fe Area 5), then `trailing`\r\n * (ms-dfd-fe/ms-ti-fe A5-01). Each was shipped as \"additive, backward\r\n * compatible\", which is true and is not the same thing as verified. This\r\n * component is consumed by ten mobile-card sites across two certified modules,\r\n * so a silent change to any slot's behaviour regresses all of them at once.\r\n *\r\n * The PROPAGATION ASYMMETRY (see below) is the single most important thing\r\n * here. It is deliberate, it is counter-intuitive, and nothing but a test\r\n * stops a future reader from \"tidying\" it into consistency and breaking every\r\n * favourite-star and picker-button site in the fleet.\r\n *\r\n * NOT COVERED — and deliberately so: R-G.3 MUST (light + dark + compact +\r\n * responsive at 320/375/414) is a VISUAL contract. jsdom computes no layout,\r\n * so asserting it here would produce tests that pass while the cards are\r\n * broken on a phone — worse than no coverage, because it would look like\r\n * coverage. R-G.3 needs a browser (Storybook + visual regression) and remains\r\n * open for all three props.\r\n */\r\ndescribe(\"MobileRowCard — slot rendering\", () => {\r\n test(\"renders title, subtitle and meta, omitting lines with no content\", () => {\r\n render(\r\n <MobileRowCard\r\n title=\"2025-IM-0042\"\r\n subtitle=\"SOFRANE SA\"\r\n meta={[\"12/05/2026\", \"3 pièces\"]}\r\n />\r\n );\r\n\r\n expect(screen.getByText(\"2025-IM-0042\")).toBeInTheDocument();\r\n expect(screen.getByText(\"SOFRANE SA\")).toBeInTheDocument();\r\n // meta joins with \" · \" into a single node\r\n expect(screen.getByText(\"12/05/2026 · 3 pièces\")).toBeInTheDocument();\r\n });\r\n\r\n test(\"meta drops null / empty entries rather than rendering stray separators\", () => {\r\n render(<MobileRowCard title=\"X\" meta={[\"Émise 01/01/2026\", null, \"\", undefined]} />);\r\n expect(screen.getByText(\"Émise 01/01/2026\")).toBeInTheDocument();\r\n expect(screen.queryByText(/·/)).not.toBeInTheDocument();\r\n });\r\n\r\n test(\"a string title gets a native tooltip; a node title does not stringify\", () => {\r\n const { rerender } = render(<MobileRowCard title=\"Long reference\" />);\r\n expect(screen.getByText(\"Long reference\")).toHaveAttribute(\"title\", \"Long reference\");\r\n\r\n rerender(<MobileRowCard title={<span>Node title</span>} />);\r\n expect(screen.getByText(\"Node title\")).toBeInTheDocument();\r\n /* The regression guarded here: wrapping a node yielded title=\"[object\r\n * Object]\". Asserted with queryByTitle rather than `.closest(\"[title]\")`\r\n * so it stays a Testing-Library query (testing-library/no-node-access).\r\n * `/./` = \"no element carries ANY non-empty title\", which is a STRONGER\r\n * assertion than the ancestor walk it replaces: MobileRowCard sets a title\r\n * attribute in exactly two places (the title Box and the subtitle Box) and\r\n * only when the value is a string, so with a node title and no subtitle the\r\n * whole tree must be title-less. */\r\n expect(screen.queryByTitle(/./)).not.toBeInTheDocument();\r\n });\r\n});\r\n\r\ndescribe(\"MobileRowCard — kv slot\", () => {\r\n test(\"renders each pair as a KV label/value unit\", () => {\r\n render(\r\n <MobileRowCard\r\n title=\"ref\"\r\n kv={[\r\n { label: \"Catégorie\", value: \"CONTINGENT\" },\r\n { label: \"Importateur\", value: \"SOFRANE SA\" },\r\n ]}\r\n />\r\n );\r\n\r\n expect(screen.getByText(\"Catégorie\")).toBeInTheDocument();\r\n expect(screen.getByText(\"CONTINGENT\")).toBeInTheDocument();\r\n expect(screen.getByText(\"Importateur\")).toBeInTheDocument();\r\n expect(screen.getByText(\"SOFRANE SA\")).toBeInTheDocument();\r\n });\r\n\r\n test(\"an empty value KEEPS the pair and shows KV's own em-dash placeholder\", () => {\r\n // This is why every consuming site could drop its `?? \\\"-\\\"`: KV owns the\r\n // placeholder. Losing this behaviour would silently reintroduce ten local\r\n // fallbacks across the fleet.\r\n render(\r\n <MobileRowCard\r\n title=\"ref\"\r\n kv={[\r\n { label: \"Bureau douanier\", value: undefined },\r\n { label: \"Marchandise\", value: \"\" },\r\n { label: \"Devise\", value: null },\r\n ]}\r\n />\r\n );\r\n\r\n expect(screen.getByText(\"Bureau douanier\")).toBeInTheDocument();\r\n expect(screen.getByText(\"Marchandise\")).toBeInTheDocument();\r\n expect(screen.getByText(\"Devise\")).toBeInTheDocument();\r\n expect(screen.getAllByText(\"—\")).toHaveLength(3);\r\n });\r\n\r\n test(\"a custom fallback overrides the em dash\", () => {\r\n render(\r\n <MobileRowCard title=\"ref\" kv={[{ label: \"Banque\", fallback: \"Non assignée\" }]} />\r\n );\r\n expect(screen.getByText(\"Non assignée\")).toBeInTheDocument();\r\n });\r\n\r\n test(\"pairs with no label are DROPPED (KV would emit a dangling aria-labelledby)\", () => {\r\n render(\r\n <MobileRowCard\r\n title=\"ref\"\r\n kv={[\r\n { label: \"\", value: \"orphan-empty\" },\r\n // `label` is required but its type (ReactNode) admits undefined, so\r\n // this is type-valid and exercises the runtime `!= null` guard.\r\n { label: undefined, value: \"orphan-missing\" },\r\n { label: \"Kept\", value: \"kept-value\" },\r\n ]}\r\n />\r\n );\r\n\r\n expect(screen.queryByText(\"orphan-empty\")).not.toBeInTheDocument();\r\n expect(screen.queryByText(\"orphan-missing\")).not.toBeInTheDocument();\r\n expect(screen.getByText(\"kept-value\")).toBeInTheDocument();\r\n });\r\n});\r\n\r\ndescribe(\"MobileRowCard — chips slot\", () => {\r\n test(\"renders chip nodes and filters falsy entries\", () => {\r\n render(\r\n <MobileRowCard\r\n title=\"ref\"\r\n chips={[<span key=\"a\">Approuvé</span>, null, false, <span key=\"b\">CO</span>]}\r\n />\r\n );\r\n expect(screen.getByText(\"Approuvé\")).toBeInTheDocument();\r\n expect(screen.getByText(\"CO\")).toBeInTheDocument();\r\n });\r\n});\r\n\r\ndescribe(\"MobileRowCard — actions (kebab)\", () => {\r\n test(\"kebab renders only when at least one non-divider action exists\", () => {\r\n const { rerender } = render(<MobileRowCard title=\"ref\" actions={[]} />);\r\n expect(screen.queryByRole(\"button\")).not.toBeInTheDocument();\r\n\r\n rerender(<MobileRowCard title=\"ref\" actions={[{ divider: true }]} />);\r\n expect(screen.queryByRole(\"button\")).not.toBeInTheDocument();\r\n\r\n rerender(\r\n <MobileRowCard\r\n title=\"ref\"\r\n actions={[{ label: \"Détail\", onClick: () => {} }]}\r\n actionsAriaLabel=\"Actions pour 2025-IM-0042\"\r\n />\r\n );\r\n expect(\r\n screen.getByRole(\"button\", { name: \"Actions pour 2025-IM-0042\" })\r\n ).toBeInTheDocument();\r\n });\r\n});\r\n\r\n/**\r\n * THE PROPAGATION CONTRACT — the reason this file matters most.\r\n *\r\n * DataGrid's mobile-card wrapper makes the whole card the click target\r\n * (role=button + onRowClick). Two slots therefore behave DIFFERENTLY on\r\n * purpose:\r\n *\r\n * leading — does NOT stop propagation. A decorative node (icon, avatar)\r\n * should still open the row like the rest of the card. Interactive\r\n * leading content is responsible for stopping propagation itself,\r\n * exactly as DataGrid's own FavoriteCell does. Get this wrong and\r\n * a tap both toggles the favourite star AND opens the drawer.\r\n *\r\n * trailing — DOES stop propagation (it shares the kebab's wrapper). Its\r\n * declared purpose is interactive actions, and a tap that both\r\n * fires the action and opens the row is the more damaging default.\r\n *\r\n * Making these two consistent — in either direction — is a breaking change for\r\n * the ten sites that depend on them.\r\n */\r\ndescribe(\"MobileRowCard — propagation asymmetry (deliberate)\", () => {\r\n test(\"leading does NOT stop click propagation to the card\", () => {\r\n const onCardClick = jest.fn();\r\n render(\r\n <div onClick={onCardClick}>\r\n <MobileRowCard title=\"ref\" leading={<span>star</span>} />\r\n </div>\r\n );\r\n\r\n fireEvent.click(screen.getByText(\"star\"));\r\n expect(onCardClick).toHaveBeenCalledTimes(1);\r\n });\r\n\r\n test(\"trailing DOES stop click propagation to the card\", () => {\r\n const onCardClick = jest.fn();\r\n const onAction = jest.fn();\r\n render(\r\n <div onClick={onCardClick}>\r\n <MobileRowCard\r\n title=\"ref\"\r\n trailing={<button onClick={onAction}>Choisir</button>}\r\n />\r\n </div>\r\n );\r\n\r\n fireEvent.click(screen.getByRole(\"button\", { name: \"Choisir\" }));\r\n expect(onAction).toHaveBeenCalledTimes(1);\r\n expect(onCardClick).not.toHaveBeenCalled();\r\n });\r\n\r\n test(\"trailing stops keydown propagation too (keyboard parity)\", () => {\r\n const onCardKeyDown = jest.fn();\r\n render(\r\n <div onKeyDown={onCardKeyDown}>\r\n <MobileRowCard title=\"ref\" trailing={<button>Choisir</button>} />\r\n </div>\r\n );\r\n\r\n fireEvent.keyDown(screen.getByRole(\"button\", { name: \"Choisir\" }), { key: \"Enter\" });\r\n expect(onCardKeyDown).not.toHaveBeenCalled();\r\n });\r\n\r\n test(\"opening the kebab does not fire the card click\", () => {\r\n const onCardClick = jest.fn();\r\n render(\r\n <div onClick={onCardClick}>\r\n <MobileRowCard title=\"ref\" actions={[{ label: \"Détail\", onClick: () => {} }]} />\r\n </div>\r\n );\r\n\r\n fireEvent.click(screen.getByRole(\"button\", { name: \"Plus d'actions\" }));\r\n expect(onCardClick).not.toHaveBeenCalled();\r\n });\r\n});\r\n\r\ndescribe(\"MobileRowCard — header composition\", () => {\r\n test(\"trailing and the kebab coexist, trailing first\", () => {\r\n render(\r\n <MobileRowCard\r\n title=\"ref\"\r\n trailing={<button>Visualiser</button>}\r\n actions={[{ label: \"Supprimer\", onClick: () => {} }]}\r\n actionsAriaLabel=\"Autres actions\"\r\n />\r\n );\r\n\r\n const visualiser = screen.getByRole(\"button\", { name: \"Visualiser\" });\r\n const kebab = screen.getByRole(\"button\", { name: \"Autres actions\" });\r\n expect(visualiser).toBeInTheDocument();\r\n expect(kebab).toBeInTheDocument();\r\n\r\n // Document order: trailing precedes the kebab in the header end cluster.\r\n expect(\r\n visualiser.compareDocumentPosition(kebab) & Node.DOCUMENT_POSITION_FOLLOWING\r\n ).toBeTruthy();\r\n });\r\n\r\n test(\"a card with ONLY trailing still renders its header\", () => {\r\n // Regression guard: hasHeader must include hasTrailing, otherwise a\r\n // title-less picker card would drop its action entirely.\r\n render(<MobileRowCard trailing={<button>Choisir</button>} />);\r\n expect(screen.getByRole(\"button\", { name: \"Choisir\" })).toBeInTheDocument();\r\n });\r\n\r\n test(\"a card with ONLY leading still renders its header\", () => {\r\n render(<MobileRowCard leading={<span>star</span>} />);\r\n expect(screen.getByText(\"star\")).toBeInTheDocument();\r\n });\r\n\r\n test(\"no props renders no header and does not throw\", () => {\r\n /* `render` not throwing IS the second half of this assertion. The button\r\n * check uses screen.queryByRole rather than container.querySelector\r\n * (testing-library/no-container + no-node-access), matching the idiom the\r\n * kebab test above already uses. */\r\n render(<MobileRowCard />);\r\n expect(screen.queryByRole(\"button\")).not.toBeInTheDocument();\r\n });\r\n});\r\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"MobileRowCard.test.js","names":["render","screen","fireEvent","MobileRowCard","jsx","_jsx","describe","test","title","subtitle","meta","expect","getByText","toBeInTheDocument","undefined","queryByText","not","_render","rerender","toHaveAttribute","children","queryByTitle","kv","label","value","getAllByText","toHaveLength","fallback","chips","_render2","actions","queryByRole","divider","onClick","actionsAriaLabel","getByRole","name","onCardClick","jest","fn","leading","click","toHaveBeenCalledTimes","onAction","trailing","toHaveBeenCalled","onCardKeyDown","onKeyDown","keyDown","key","visualiser","kebab","compareDocumentPosition","Node","DOCUMENT_POSITION_FOLLOWING","toBeTruthy"],"sources":["../../../../src/lib/v2/__tests__/MobileRowCard.test.tsx"],"sourcesContent":["import \"@testing-library/jest-dom\";\r\nimport { render, screen, fireEvent } from \"@testing-library/react\";\r\nimport MobileRowCard from \"../components/MobileRowCard\";\r\n\r\n/**\r\n * MobileRowCard — slot contract tests.\r\n *\r\n * WHY THIS FILE EXISTS\r\n * --------------------\r\n * §10.3 R-G.6 MUST — \"Snapshot Storybook + tests Jest/Testing-Library\" — was\r\n * unmet for THREE props added to this component across successive module\r\n * audits: `kv` and `leading` (ms-dfd-fe Area 5), then `trailing`\r\n * (ms-dfd-fe/ms-ti-fe A5-01). Each was shipped as \"additive, backward\r\n * compatible\", which is true and is not the same thing as verified. This\r\n * component is consumed by ten mobile-card sites across two certified modules,\r\n * so a silent change to any slot's behaviour regresses all of them at once.\r\n *\r\n * The PROPAGATION ASYMMETRY (see below) is the single most important thing\r\n * here. It is deliberate, it is counter-intuitive, and nothing but a test\r\n * stops a future reader from \"tidying\" it into consistency and breaking every\r\n * favourite-star and picker-button site in the fleet.\r\n *\r\n * NOT COVERED — and deliberately so: R-G.3 MUST (light + dark + compact +\r\n * responsive at 320/375/414) is a VISUAL contract. jsdom computes no layout,\r\n * so asserting it here would produce tests that pass while the cards are\r\n * broken on a phone — worse than no coverage, because it would look like\r\n * coverage. R-G.3 needs a browser (Storybook + visual regression) and remains\r\n * open for all three props.\r\n */\r\ndescribe(\"MobileRowCard — slot rendering\", () => {\r\n test(\"renders title, subtitle and meta, omitting lines with no content\", () => {\r\n render(\r\n <MobileRowCard\r\n title=\"2025-IM-0042\"\r\n subtitle=\"SOFRANE SA\"\r\n meta={[\"12/05/2026\", \"3 pièces\"]}\r\n />\r\n );\r\n\r\n expect(screen.getByText(\"2025-IM-0042\")).toBeInTheDocument();\r\n expect(screen.getByText(\"SOFRANE SA\")).toBeInTheDocument();\r\n // meta joins with \" · \" into a single node\r\n expect(screen.getByText(\"12/05/2026 · 3 pièces\")).toBeInTheDocument();\r\n });\r\n\r\n test(\"meta drops null / empty entries rather than rendering stray separators\", () => {\r\n render(<MobileRowCard title=\"X\" meta={[\"Émise 01/01/2026\", null, \"\", undefined]} />);\r\n expect(screen.getByText(\"Émise 01/01/2026\")).toBeInTheDocument();\r\n expect(screen.queryByText(/·/)).not.toBeInTheDocument();\r\n });\r\n\r\n test(\"a string title gets a native tooltip; a node title does not stringify\", () => {\r\n const { rerender } = render(<MobileRowCard title=\"Long reference\" />);\r\n expect(screen.getByText(\"Long reference\")).toHaveAttribute(\"title\", \"Long reference\");\r\n\r\n rerender(<MobileRowCard title={<span>Node title</span>} />);\r\n expect(screen.getByText(\"Node title\")).toBeInTheDocument();\r\n /* The regression guarded here: wrapping a node yielded title=\"[object\r\n * Object]\". Asserted with queryByTitle rather than `.closest(\"[title]\")`\r\n * so it stays a Testing-Library query (testing-library/no-node-access).\r\n * `/./` = \"no element carries ANY non-empty title\", which is a STRONGER\r\n * assertion than the ancestor walk it replaces: MobileRowCard sets a title\r\n * attribute in exactly two places (the title Box and the subtitle Box) and\r\n * only when the value is a string, so with a node title and no subtitle the\r\n * whole tree must be title-less. */\r\n expect(screen.queryByTitle(/./)).not.toBeInTheDocument();\r\n });\r\n});\r\n\r\ndescribe(\"MobileRowCard — kv slot\", () => {\r\n test(\"renders each pair as a KV label/value unit\", () => {\r\n render(\r\n <MobileRowCard\r\n title=\"ref\"\r\n kv={[\r\n { label: \"Catégorie\", value: \"CONTINGENT\" },\r\n { label: \"Importateur\", value: \"SOFRANE SA\" },\r\n ]}\r\n />\r\n );\r\n\r\n expect(screen.getByText(\"Catégorie\")).toBeInTheDocument();\r\n expect(screen.getByText(\"CONTINGENT\")).toBeInTheDocument();\r\n expect(screen.getByText(\"Importateur\")).toBeInTheDocument();\r\n expect(screen.getByText(\"SOFRANE SA\")).toBeInTheDocument();\r\n });\r\n\r\n test(\"an empty value KEEPS the pair and shows KV's own em-dash placeholder\", () => {\r\n // This is why every consuming site could drop its `?? \\\"-\\\"`: KV owns the\r\n // placeholder. Losing this behaviour would silently reintroduce ten local\r\n // fallbacks across the fleet.\r\n render(\r\n <MobileRowCard\r\n title=\"ref\"\r\n kv={[\r\n { label: \"Bureau douanier\", value: undefined },\r\n { label: \"Marchandise\", value: \"\" },\r\n { label: \"Devise\", value: null },\r\n ]}\r\n />\r\n );\r\n\r\n expect(screen.getByText(\"Bureau douanier\")).toBeInTheDocument();\r\n expect(screen.getByText(\"Marchandise\")).toBeInTheDocument();\r\n expect(screen.getByText(\"Devise\")).toBeInTheDocument();\r\n expect(screen.getAllByText(\"—\")).toHaveLength(3);\r\n });\r\n\r\n test(\"a custom fallback overrides the em dash\", () => {\r\n render(\r\n <MobileRowCard title=\"ref\" kv={[{ label: \"Banque\", fallback: \"Non assignée\" }]} />\r\n );\r\n expect(screen.getByText(\"Non assignée\")).toBeInTheDocument();\r\n });\r\n\r\n test(\"pairs with no label are DROPPED (KV would emit a dangling aria-labelledby)\", () => {\r\n render(\r\n <MobileRowCard\r\n title=\"ref\"\r\n kv={[\r\n { label: \"\", value: \"orphan-empty\" },\r\n // `label` is required but its type (ReactNode) admits undefined, so\r\n // this is type-valid and exercises the runtime `!= null` guard.\r\n { label: undefined, value: \"orphan-missing\" },\r\n { label: \"Kept\", value: \"kept-value\" },\r\n ]}\r\n />\r\n );\r\n\r\n expect(screen.queryByText(\"orphan-empty\")).not.toBeInTheDocument();\r\n expect(screen.queryByText(\"orphan-missing\")).not.toBeInTheDocument();\r\n expect(screen.getByText(\"kept-value\")).toBeInTheDocument();\r\n });\r\n});\r\n\r\ndescribe(\"MobileRowCard — chips slot\", () => {\r\n test(\"renders chip nodes and filters falsy entries\", () => {\r\n render(\r\n <MobileRowCard\r\n title=\"ref\"\r\n chips={[<span key=\"a\">Approuvé</span>, null, false, <span key=\"b\">CO</span>]}\r\n />\r\n );\r\n expect(screen.getByText(\"Approuvé\")).toBeInTheDocument();\r\n expect(screen.getByText(\"CO\")).toBeInTheDocument();\r\n });\r\n});\r\n\r\ndescribe(\"MobileRowCard — actions (kebab)\", () => {\r\n test(\"kebab renders only when at least one non-divider action exists\", () => {\r\n const { rerender } = render(<MobileRowCard title=\"ref\" actions={[]} />);\r\n expect(screen.queryByRole(\"button\")).not.toBeInTheDocument();\r\n\r\n rerender(<MobileRowCard title=\"ref\" actions={[{ divider: true }]} />);\r\n expect(screen.queryByRole(\"button\")).not.toBeInTheDocument();\r\n\r\n rerender(\r\n <MobileRowCard\r\n title=\"ref\"\r\n actions={[{ label: \"Détail\", onClick: () => {} }]}\r\n actionsAriaLabel=\"Actions pour 2025-IM-0042\"\r\n />\r\n );\r\n expect(\r\n screen.getByRole(\"button\", { name: \"Actions pour 2025-IM-0042\" })\r\n ).toBeInTheDocument();\r\n });\r\n});\r\n\r\n/**\r\n * THE PROPAGATION CONTRACT — the reason this file matters most.\r\n *\r\n * DataGrid's mobile-card wrapper makes the whole card the click target\r\n * (role=button + onRowClick). Two slots therefore behave DIFFERENTLY on\r\n * purpose:\r\n *\r\n * leading — does NOT stop propagation. A decorative node (icon, avatar)\r\n * should still open the row like the rest of the card. Interactive\r\n * leading content is responsible for stopping propagation itself,\r\n * exactly as DataGrid's own FavoriteCell does. Get this wrong and\r\n * a tap both toggles the favourite star AND opens the drawer.\r\n *\r\n * trailing — DOES stop propagation (it shares the kebab's wrapper). Its\r\n * declared purpose is interactive actions, and a tap that both\r\n * fires the action and opens the row is the more damaging default.\r\n *\r\n * Making these two consistent — in either direction — is a breaking change for\r\n * the ten sites that depend on them.\r\n */\r\ndescribe(\"MobileRowCard — propagation asymmetry (deliberate)\", () => {\r\n test(\"leading does NOT stop click propagation to the card\", () => {\r\n const onCardClick = jest.fn();\r\n render(\r\n <div onClick={onCardClick}>\r\n <MobileRowCard title=\"ref\" leading={<span>star</span>} />\r\n </div>\r\n );\r\n\r\n fireEvent.click(screen.getByText(\"star\"));\r\n expect(onCardClick).toHaveBeenCalledTimes(1);\r\n });\r\n\r\n test(\"trailing DOES stop click propagation to the card\", () => {\r\n const onCardClick = jest.fn();\r\n const onAction = jest.fn();\r\n render(\r\n <div onClick={onCardClick}>\r\n <MobileRowCard\r\n title=\"ref\"\r\n trailing={<button onClick={onAction}>Choisir</button>}\r\n />\r\n </div>\r\n );\r\n\r\n fireEvent.click(screen.getByRole(\"button\", { name: \"Choisir\" }));\r\n expect(onAction).toHaveBeenCalledTimes(1);\r\n expect(onCardClick).not.toHaveBeenCalled();\r\n });\r\n\r\n test(\"trailing stops keydown propagation too (keyboard parity)\", () => {\r\n const onCardKeyDown = jest.fn();\r\n render(\r\n <div onKeyDown={onCardKeyDown}>\r\n <MobileRowCard title=\"ref\" trailing={<button>Choisir</button>} />\r\n </div>\r\n );\r\n\r\n fireEvent.keyDown(screen.getByRole(\"button\", { name: \"Choisir\" }), { key: \"Enter\" });\r\n expect(onCardKeyDown).not.toHaveBeenCalled();\r\n });\r\n\r\n test(\"opening the kebab does not fire the card click\", () => {\r\n const onCardClick = jest.fn();\r\n render(\r\n <div onClick={onCardClick}>\r\n <MobileRowCard title=\"ref\" actions={[{ label: \"Détail\", onClick: () => {} }]} />\r\n </div>\r\n );\r\n\r\n fireEvent.click(screen.getByRole(\"button\", { name: \"Plus d'actions\" }));\r\n expect(onCardClick).not.toHaveBeenCalled();\r\n });\r\n});\r\n\r\ndescribe(\"MobileRowCard — header composition\", () => {\r\n test(\"trailing and the kebab coexist, trailing first\", () => {\r\n render(\r\n <MobileRowCard\r\n title=\"ref\"\r\n trailing={<button>Visualiser</button>}\r\n actions={[{ label: \"Supprimer\", onClick: () => {} }]}\r\n actionsAriaLabel=\"Autres actions\"\r\n />\r\n );\r\n\r\n const visualiser = screen.getByRole(\"button\", { name: \"Visualiser\" });\r\n const kebab = screen.getByRole(\"button\", { name: \"Autres actions\" });\r\n expect(visualiser).toBeInTheDocument();\r\n expect(kebab).toBeInTheDocument();\r\n\r\n // Document order: trailing precedes the kebab in the header end cluster.\r\n expect(\r\n visualiser.compareDocumentPosition(kebab) & Node.DOCUMENT_POSITION_FOLLOWING\r\n ).toBeTruthy();\r\n });\r\n\r\n test(\"a card with ONLY trailing still renders its header\", () => {\r\n // Regression guard: hasHeader must include hasTrailing, otherwise a\r\n // title-less picker card would drop its action entirely.\r\n render(<MobileRowCard trailing={<button>Choisir</button>} />);\r\n expect(screen.getByRole(\"button\", { name: \"Choisir\" })).toBeInTheDocument();\r\n });\r\n\r\n test(\"a card with ONLY leading still renders its header\", () => {\r\n render(<MobileRowCard leading={<span>star</span>} />);\r\n expect(screen.getByText(\"star\")).toBeInTheDocument();\r\n });\r\n\r\n test(\"no props renders no header and does not throw\", () => {\r\n /* `render` not throwing IS the second half of this assertion. The button\r\n * check uses screen.queryByRole rather than container.querySelector\r\n * (testing-library/no-container + no-node-access), matching the idiom the\r\n * kebab test above already uses. */\r\n render(<MobileRowCard />);\r\n expect(screen.queryByRole(\"button\")).not.toBeInTheDocument();\r\n });\r\n});\r\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SectionErrorBoundary.test.js","names":["React","render","screen","fireEvent","SectionErrorBoundary","jsx","_jsx","jsxs","_jsxs","Boom","_ref","_ref$shouldThrow","shouldThrow","Error","children","consoleErrorSpy","beforeEach","jest","spyOn","console","mockImplementation","afterEach","mockRestore","describe","test","title","expect","getByText","toBeInTheDocument","queryByRole","not","getByRole","toHaveTextContent","previous","process","env","holder","Flaky","click","name","retryLabel","onError","fn","toHaveBeenCalledTimes","_ref2","mock","calls","_ref3","_slicedToArray","err","info","toBeInstanceOf","message","toMatch","toHaveProperty","toThrow","onEscape","secondaryAction","label","onClick","escape","logged","some","args","a","includes","toBe"],"sources":["../../../../src/lib/v2/__tests__/SectionErrorBoundary.test.tsx"],"sourcesContent":["import \"@testing-library/jest-dom\";\r\nimport * as React from \"react\";\r\nimport { render, screen, fireEvent } from \"@testing-library/react\";\r\nimport SectionErrorBoundary from \"../components/SectionErrorBoundary\";\r\n\r\n/**\r\n * SectionErrorBoundary — behaviour tests.\r\n *\r\n * WHY THIS FILE MATTERS MORE THAN MOST\r\n * ------------------------------------\r\n * §10.3 R-G.6 MUST, but the real reason is that this is the one component in the\r\n * library whose ENTIRE contract is a failure path. Every other component can be\r\n * eyeballed in Storybook; this one only does its job when something throws, which\r\n * nobody exercises by hand. And its failure mode is total: if the boundary does\r\n * not catch, React unmounts the whole tree and the user gets a blank screen\r\n * (ISO/IEC 25010 § Reliability — fault tolerance).\r\n *\r\n * jsdom is the right tool here, unusually: catching, re-mounting, logging and\r\n * conditional detail are all logic, not layout.\r\n *\r\n * NOTE ON CONSOLE NOISE — React logs caught errors to console.error itself, on\r\n * top of the boundary's own log. Both are silenced per-test rather than globally\r\n * so a genuine unexpected console.error still surfaces in the other suites.\r\n */\r\n\r\n/** A component that throws on render, controlled by a prop. */\r\nfunction Boom({ shouldThrow = true }: { shouldThrow?: boolean }) {\r\n if (shouldThrow) throw new Error(\"payload inattendu: articles.map n'est pas une fonction\");\r\n return <div>contenu rétabli</div>;\r\n}\r\n\r\nlet consoleErrorSpy: jest.SpyInstance;\r\n\r\nbeforeEach(() => {\r\n consoleErrorSpy = jest.spyOn(console, \"error\").mockImplementation(() => {});\r\n});\r\n\r\nafterEach(() => {\r\n consoleErrorSpy.mockRestore();\r\n});\r\n\r\ndescribe(\"SectionErrorBoundary — containment\", () => {\r\n test(\"renders children untouched when nothing throws\", () => {\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\">\r\n <div>contenu normal</div>\r\n </SectionErrorBoundary>\r\n );\r\n expect(screen.getByText(\"contenu normal\")).toBeInTheDocument();\r\n expect(screen.queryByRole(\"alert\")).not.toBeInTheDocument();\r\n });\r\n\r\n test(\"catches a render exception and keeps its SIBLINGS mounted\", () => {\r\n /* THE contract. The assertion that matters is not that the banner appears —\r\n * it is that `sibling` is still in the document. Without a boundary React\r\n * unmounts the entire tree and BOTH would be gone: that is the blank screen\r\n * this component exists to prevent. */\r\n render(\r\n <div>\r\n <div>sibling</div>\r\n <SectionErrorBoundary title=\"Marchandises\">\r\n <Boom />\r\n </SectionErrorBoundary>\r\n </div>\r\n );\r\n\r\n expect(screen.getByText(\"sibling\")).toBeInTheDocument();\r\n expect(screen.getByRole(\"alert\")).toBeInTheDocument();\r\n });\r\n\r\n test(\"names the failing section so the user knows WHAT broke\", () => {\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\">\r\n <Boom />\r\n </SectionErrorBoundary>\r\n );\r\n expect(screen.getByRole(\"alert\")).toHaveTextContent(\"Marchandises\");\r\n });\r\n\r\n test(\"falls back to a generic label when no title is supplied\", () => {\r\n render(\r\n <SectionErrorBoundary>\r\n <Boom />\r\n </SectionErrorBoundary>\r\n );\r\n expect(screen.getByRole(\"alert\")).toHaveTextContent(\"Section\");\r\n });\r\n});\r\n\r\ndescribe(\"SectionErrorBoundary — user-facing copy (§09.3)\", () => {\r\n test(\"shows French recovery copy, never a raw exception message\", () => {\r\n /* Charter §09.3 forbids exposing technical detail to the user, and a raw JS\r\n * exception is worse than the \"Erreur HTTP 422\" the charter uses as its DON'T\r\n * example. The reference implementation this was promoted from rendered\r\n * error.message straight into the banner body; this asserts we do not.\r\n *\r\n * NODE_ENV is \"test\" here, not \"production\", so the dev detail block DOES\r\n * render — which is why this test targets the recovery sentence rather than\r\n * asserting the absence of the message. The production-only behaviour is\r\n * asserted separately below. */\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\">\r\n <Boom />\r\n </SectionErrorBoundary>\r\n );\r\n expect(screen.getByRole(\"alert\")).toHaveTextContent(\r\n /Cette section n'a pas pu s'afficher/\r\n );\r\n });\r\n\r\n test(\"suppresses the technical detail in production\", () => {\r\n const previous = process.env[\"NODE_ENV\"];\r\n /* Bracket access throughout, matching the component: `process.env` is an index\r\n * signature and `noPropertyAccessFromIndexSignature` is mandatory. Tests are\r\n * outside tsconfig.build.json's scope so dot access would have compiled here —\r\n * two conventions in neighbouring files is the drift this audit exists to stop.\r\n *\r\n * Assigned rather than replaced wholesale: other suites read NODE_ENV, and the\r\n * finally block restores it even if the assertion throws. */\r\n (process.env as Record<string, string>)[\"NODE_ENV\"] = \"production\";\r\n try {\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\">\r\n <Boom />\r\n </SectionErrorBoundary>\r\n );\r\n expect(screen.getByRole(\"alert\")).toHaveTextContent(\r\n /Cette section n'a pas pu s'afficher/\r\n );\r\n expect(screen.getByRole(\"alert\")).not.toHaveTextContent(/articles\\.map/);\r\n } finally {\r\n (process.env as Record<string, string>)[\"NODE_ENV\"] = previous ?? \"test\";\r\n }\r\n });\r\n});\r\n\r\ndescribe(\"SectionErrorBoundary — retry REMOUNTS\", () => {\r\n test(\"retry clears the error and re-renders the subtree\", () => {\r\n /* The re-key on `attempt` is what makes this work. Without it React reuses\r\n * the instance that threw and retry fails identically — a retry button that\r\n * cannot succeed is worse than no retry button. `shouldThrow` is read from a\r\n * mutable holder so the same element throws first and succeeds second,\r\n * exactly as a transient payload problem would. */\r\n const holder = { shouldThrow: true };\r\n function Flaky() {\r\n return <Boom shouldThrow={holder.shouldThrow} />;\r\n }\r\n\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\">\r\n <Flaky />\r\n </SectionErrorBoundary>\r\n );\r\n expect(screen.getByRole(\"alert\")).toBeInTheDocument();\r\n\r\n holder.shouldThrow = false;\r\n fireEvent.click(screen.getByRole(\"button\", { name: /Réessayer/ }));\r\n\r\n expect(screen.getByText(\"contenu rétabli\")).toBeInTheDocument();\r\n expect(screen.queryByRole(\"alert\")).not.toBeInTheDocument();\r\n });\r\n\r\n test(\"a custom retryLabel is used on the button and in the copy\", () => {\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\" retryLabel=\"Recharger\">\r\n <Boom />\r\n </SectionErrorBoundary>\r\n );\r\n expect(screen.getByRole(\"button\", { name: /Recharger/ })).toBeInTheDocument();\r\n expect(screen.getByRole(\"alert\")).toHaveTextContent(/Recharger/);\r\n });\r\n});\r\n\r\ndescribe(\"SectionErrorBoundary — reporting and second exit\", () => {\r\n test(\"calls onError with the error and the component info\", () => {\r\n /* console.error is invisible in production, so without this hook a boundary\r\n * silently swallows the only signal an operator would ever get. */\r\n const onError = jest.fn();\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\" onError={onError}>\r\n <Boom />\r\n </SectionErrorBoundary>\r\n );\r\n\r\n expect(onError).toHaveBeenCalledTimes(1);\r\n const [err, info] = onError.mock.calls[0] as [Error, React.ErrorInfo];\r\n expect(err).toBeInstanceOf(Error);\r\n expect(err.message).toMatch(/articles\\.map/);\r\n expect(info).toHaveProperty(\"componentStack\");\r\n });\r\n\r\n test(\"a throwing onError does not take the boundary down with it\", () => {\r\n /* Guard on the guard. A reporter that throws must not defeat the component\r\n * whose entire purpose is stopping one failure from causing another. */\r\n const onError = jest.fn(() => {\r\n throw new Error(\"APM indisponible\");\r\n });\r\n\r\n expect(() =>\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\" onError={onError}>\r\n <Boom />\r\n </SectionErrorBoundary>\r\n )\r\n ).not.toThrow();\r\n\r\n expect(screen.getByRole(\"alert\")).toBeInTheDocument();\r\n });\r\n\r\n test(\"renders the optional secondary exit when supplied\", () => {\r\n /* Only possible since Banner gained secondaryAction (gap G-19). The reference\r\n * implementation offered no way out when retrying could not help. */\r\n const onEscape = jest.fn();\r\n render(\r\n <SectionErrorBoundary\r\n title=\"Marchandises\"\r\n secondaryAction={{ label: \"Retour à la liste\", onClick: onEscape }}\r\n >\r\n <Boom />\r\n </SectionErrorBoundary>\r\n );\r\n\r\n const escape = screen.getByRole(\"button\", { name: /Retour à la liste/ });\r\n fireEvent.click(escape);\r\n expect(onEscape).toHaveBeenCalledTimes(1);\r\n });\r\n\r\n test(\"logs the section title and the component stack\", () => {\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\">\r\n <Boom />\r\n </SectionErrorBoundary>\r\n );\r\n /* Asserted loosely — the point is that an operator grepping logs can find\r\n * WHICH section failed, not the exact format of the line. */\r\n const logged = consoleErrorSpy.mock.calls.some((args) =>\r\n args.some((a) => typeof a === \"string\" && a.includes(\"Marchandises\"))\r\n );\r\n expect(logged).toBe(true);\r\n });\r\n});\r\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"SectionErrorBoundary.test.js","names":["React","render","screen","fireEvent","SectionErrorBoundary","jsx","_jsx","jsxs","_jsxs","Boom","_ref","_ref$shouldThrow","shouldThrow","Error","children","consoleErrorSpy","beforeEach","jest","spyOn","console","mockImplementation","afterEach","mockRestore","describe","test","title","expect","getByText","toBeInTheDocument","queryByRole","not","getByRole","toHaveTextContent","previous","process","env","holder","Flaky","click","name","retryLabel","onError","fn","toHaveBeenCalledTimes","_ref2","mock","calls","_ref3","_slicedToArray","err","info","toBeInstanceOf","message","toMatch","toHaveProperty","toThrow","onEscape","secondaryAction","label","onClick","escape","logged","some","args","a","includes","toBe"],"sources":["../../../../src/lib/v2/__tests__/SectionErrorBoundary.test.tsx"],"sourcesContent":["import \"@testing-library/jest-dom\";\r\nimport * as React from \"react\";\r\nimport { render, screen, fireEvent } from \"@testing-library/react\";\r\nimport SectionErrorBoundary from \"../components/SectionErrorBoundary\";\r\n\r\n/**\r\n * SectionErrorBoundary — behaviour tests.\r\n *\r\n * WHY THIS FILE MATTERS MORE THAN MOST\r\n * ------------------------------------\r\n * §10.3 R-G.6 MUST, but the real reason is that this is the one component in the\r\n * library whose ENTIRE contract is a failure path. Every other component can be\r\n * eyeballed in Storybook; this one only does its job when something throws, which\r\n * nobody exercises by hand. And its failure mode is total: if the boundary does\r\n * not catch, React unmounts the whole tree and the user gets a blank screen\r\n * (ISO/IEC 25010 § Reliability — fault tolerance).\r\n *\r\n * jsdom is the right tool here, unusually: catching, re-mounting, logging and\r\n * conditional detail are all logic, not layout.\r\n *\r\n * NOTE ON CONSOLE NOISE — React logs caught errors to console.error itself, on\r\n * top of the boundary's own log. Both are silenced per-test rather than globally\r\n * so a genuine unexpected console.error still surfaces in the other suites.\r\n */\r\n\r\n/** A component that throws on render, controlled by a prop. */\r\nfunction Boom({ shouldThrow = true }: { shouldThrow?: boolean }) {\r\n if (shouldThrow) throw new Error(\"payload inattendu: articles.map n'est pas une fonction\");\r\n return <div>contenu rétabli</div>;\r\n}\r\n\r\nlet consoleErrorSpy: jest.SpyInstance;\r\n\r\nbeforeEach(() => {\r\n consoleErrorSpy = jest.spyOn(console, \"error\").mockImplementation(() => {});\r\n});\r\n\r\nafterEach(() => {\r\n consoleErrorSpy.mockRestore();\r\n});\r\n\r\ndescribe(\"SectionErrorBoundary — containment\", () => {\r\n test(\"renders children untouched when nothing throws\", () => {\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\">\r\n <div>contenu normal</div>\r\n </SectionErrorBoundary>\r\n );\r\n expect(screen.getByText(\"contenu normal\")).toBeInTheDocument();\r\n expect(screen.queryByRole(\"alert\")).not.toBeInTheDocument();\r\n });\r\n\r\n test(\"catches a render exception and keeps its SIBLINGS mounted\", () => {\r\n /* THE contract. The assertion that matters is not that the banner appears —\r\n * it is that `sibling` is still in the document. Without a boundary React\r\n * unmounts the entire tree and BOTH would be gone: that is the blank screen\r\n * this component exists to prevent. */\r\n render(\r\n <div>\r\n <div>sibling</div>\r\n <SectionErrorBoundary title=\"Marchandises\">\r\n <Boom />\r\n </SectionErrorBoundary>\r\n </div>\r\n );\r\n\r\n expect(screen.getByText(\"sibling\")).toBeInTheDocument();\r\n expect(screen.getByRole(\"alert\")).toBeInTheDocument();\r\n });\r\n\r\n test(\"names the failing section so the user knows WHAT broke\", () => {\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\">\r\n <Boom />\r\n </SectionErrorBoundary>\r\n );\r\n expect(screen.getByRole(\"alert\")).toHaveTextContent(\"Marchandises\");\r\n });\r\n\r\n test(\"falls back to a generic label when no title is supplied\", () => {\r\n render(\r\n <SectionErrorBoundary>\r\n <Boom />\r\n </SectionErrorBoundary>\r\n );\r\n expect(screen.getByRole(\"alert\")).toHaveTextContent(\"Section\");\r\n });\r\n});\r\n\r\ndescribe(\"SectionErrorBoundary — user-facing copy (§09.3)\", () => {\r\n test(\"shows French recovery copy, never a raw exception message\", () => {\r\n /* Charter §09.3 forbids exposing technical detail to the user, and a raw JS\r\n * exception is worse than the \"Erreur HTTP 422\" the charter uses as its DON'T\r\n * example. The reference implementation this was promoted from rendered\r\n * error.message straight into the banner body; this asserts we do not.\r\n *\r\n * NODE_ENV is \"test\" here, not \"production\", so the dev detail block DOES\r\n * render — which is why this test targets the recovery sentence rather than\r\n * asserting the absence of the message. The production-only behaviour is\r\n * asserted separately below. */\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\">\r\n <Boom />\r\n </SectionErrorBoundary>\r\n );\r\n expect(screen.getByRole(\"alert\")).toHaveTextContent(\r\n /Cette section n'a pas pu s'afficher/\r\n );\r\n });\r\n\r\n test(\"suppresses the technical detail in production\", () => {\r\n const previous = process.env[\"NODE_ENV\"];\r\n /* Bracket access throughout, matching the component: `process.env` is an index\r\n * signature and `noPropertyAccessFromIndexSignature` is mandatory. Tests are\r\n * outside tsconfig.build.json's scope so dot access would have compiled here —\r\n * two conventions in neighbouring files is the drift this audit exists to stop.\r\n *\r\n * Assigned rather than replaced wholesale: other suites read NODE_ENV, and the\r\n * finally block restores it even if the assertion throws. */\r\n (process.env as Record<string, string>)[\"NODE_ENV\"] = \"production\";\r\n try {\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\">\r\n <Boom />\r\n </SectionErrorBoundary>\r\n );\r\n expect(screen.getByRole(\"alert\")).toHaveTextContent(\r\n /Cette section n'a pas pu s'afficher/\r\n );\r\n expect(screen.getByRole(\"alert\")).not.toHaveTextContent(/articles\\.map/);\r\n } finally {\r\n (process.env as Record<string, string>)[\"NODE_ENV\"] = previous ?? \"test\";\r\n }\r\n });\r\n});\r\n\r\ndescribe(\"SectionErrorBoundary — retry REMOUNTS\", () => {\r\n test(\"retry clears the error and re-renders the subtree\", () => {\r\n /* The re-key on `attempt` is what makes this work. Without it React reuses\r\n * the instance that threw and retry fails identically — a retry button that\r\n * cannot succeed is worse than no retry button. `shouldThrow` is read from a\r\n * mutable holder so the same element throws first and succeeds second,\r\n * exactly as a transient payload problem would. */\r\n const holder = { shouldThrow: true };\r\n function Flaky() {\r\n return <Boom shouldThrow={holder.shouldThrow} />;\r\n }\r\n\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\">\r\n <Flaky />\r\n </SectionErrorBoundary>\r\n );\r\n expect(screen.getByRole(\"alert\")).toBeInTheDocument();\r\n\r\n holder.shouldThrow = false;\r\n fireEvent.click(screen.getByRole(\"button\", { name: /Réessayer/ }));\r\n\r\n expect(screen.getByText(\"contenu rétabli\")).toBeInTheDocument();\r\n expect(screen.queryByRole(\"alert\")).not.toBeInTheDocument();\r\n });\r\n\r\n test(\"a custom retryLabel is used on the button and in the copy\", () => {\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\" retryLabel=\"Recharger\">\r\n <Boom />\r\n </SectionErrorBoundary>\r\n );\r\n expect(screen.getByRole(\"button\", { name: /Recharger/ })).toBeInTheDocument();\r\n expect(screen.getByRole(\"alert\")).toHaveTextContent(/Recharger/);\r\n });\r\n});\r\n\r\ndescribe(\"SectionErrorBoundary — reporting and second exit\", () => {\r\n test(\"calls onError with the error and the component info\", () => {\r\n /* console.error is invisible in production, so without this hook a boundary\r\n * silently swallows the only signal an operator would ever get. */\r\n const onError = jest.fn();\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\" onError={onError}>\r\n <Boom />\r\n </SectionErrorBoundary>\r\n );\r\n\r\n expect(onError).toHaveBeenCalledTimes(1);\r\n const [err, info] = onError.mock.calls[0] as [Error, React.ErrorInfo];\r\n expect(err).toBeInstanceOf(Error);\r\n expect(err.message).toMatch(/articles\\.map/);\r\n expect(info).toHaveProperty(\"componentStack\");\r\n });\r\n\r\n test(\"a throwing onError does not take the boundary down with it\", () => {\r\n /* Guard on the guard. A reporter that throws must not defeat the component\r\n * whose entire purpose is stopping one failure from causing another. */\r\n const onError = jest.fn(() => {\r\n throw new Error(\"APM indisponible\");\r\n });\r\n\r\n expect(() =>\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\" onError={onError}>\r\n <Boom />\r\n </SectionErrorBoundary>\r\n )\r\n ).not.toThrow();\r\n\r\n expect(screen.getByRole(\"alert\")).toBeInTheDocument();\r\n });\r\n\r\n test(\"renders the optional secondary exit when supplied\", () => {\r\n /* Only possible since Banner gained secondaryAction (gap G-19). The reference\r\n * implementation offered no way out when retrying could not help. */\r\n const onEscape = jest.fn();\r\n render(\r\n <SectionErrorBoundary\r\n title=\"Marchandises\"\r\n secondaryAction={{ label: \"Retour à la liste\", onClick: onEscape }}\r\n >\r\n <Boom />\r\n </SectionErrorBoundary>\r\n );\r\n\r\n const escape = screen.getByRole(\"button\", { name: /Retour à la liste/ });\r\n fireEvent.click(escape);\r\n expect(onEscape).toHaveBeenCalledTimes(1);\r\n });\r\n\r\n test(\"logs the section title and the component stack\", () => {\r\n render(\r\n <SectionErrorBoundary title=\"Marchandises\">\r\n <Boom />\r\n </SectionErrorBoundary>\r\n );\r\n /* Asserted loosely — the point is that an operator grepping logs can find\r\n * WHICH section failed, not the exact format of the line. */\r\n const logged = consoleErrorSpy.mock.calls.some((args) =>\r\n args.some((a: unknown) => typeof a === \"string\" && a.includes(\"Marchandises\"))\r\n );\r\n expect(logged).toBe(true);\r\n });\r\n});\r\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"labels-merge.test.js","names":["
|
|
1
|
+
{"version":3,"file":"labels-merge.test.js","names":["render","screen","DataGrid","jsx","_jsx","describe","baseRows","Array","from","length","_","i","id","test","rows","columns","key","header","pagination","page","pageSize","total","onPageChange","expect","getByLabelText","toBeInTheDocument","labels","firstPage","onSelectionChange","tableAria","ariaLabel","queryByLabelText","not","rangeOf","to","concat","getByText"],"sources":["../../../../src/lib/v2/__tests__/labels-merge.test.tsx"],"sourcesContent":["import \"@testing-library/jest-dom\";\r\nimport { render, screen } from \"@testing-library/react\";\r\nimport DataGrid from \"../patterns/DataGrid\";\r\n\r\n/**\r\n * Verifies the deep-merge contract for the `labels` prop pattern\r\n * shared by 17 components / patterns in v2 (Batches 4-7).\r\n *\r\n * DataGrid is the canonical test subject because it has the deepest\r\n * label surface (top-level keys + nested `pagination.*`). The\r\n * Object.freeze on DEFAULT_LABELS protects against accidental\r\n * mutation; the mergeLabels helper must spread the nested object,\r\n * not replace it.\r\n */\r\ninterface Row {\r\n id: number;\r\n}\r\n\r\ndescribe(\"DataGrid — labels deep-merge contract\", () => {\r\n const baseRows: Row[] = Array.from({ length: 10 }, (_, i): Row => ({\r\n id: i + 1,\r\n }));\r\n\r\n test(\"with no labels prop, all defaults render in French\", () => {\r\n render(\r\n <DataGrid\r\n rows={baseRows}\r\n columns={[{ key: \"id\", header: \"ID\" }]}\r\n pagination={{ page: 1, pageSize: 25, total: 100, onPageChange: () => {} }}\r\n />\r\n );\r\n\r\n // Pagination FR defaults\r\n expect(screen.getByLabelText(\"Première page\")).toBeInTheDocument();\r\n expect(screen.getByLabelText(\"Page précédente\")).toBeInTheDocument();\r\n expect(screen.getByLabelText(\"Page suivante\")).toBeInTheDocument();\r\n expect(screen.getByLabelText(\"Dernière page\")).toBeInTheDocument();\r\n });\r\n\r\n test(\"partial labels.pagination merges onto FR defaults — overridden keys win\", () => {\r\n render(\r\n <DataGrid\r\n rows={baseRows}\r\n columns={[{ key: \"id\", header: \"ID\" }]}\r\n pagination={{ page: 1, pageSize: 25, total: 100, onPageChange: () => {} }}\r\n labels={{\r\n pagination: {\r\n firstPage: \"First!\",\r\n // lastPage, previousPage, nextPage, loading, rowsPerPage,\r\n // rangeOf NOT provided — must fall back to FR defaults.\r\n },\r\n }}\r\n />\r\n );\r\n\r\n // Custom override applied\r\n expect(screen.getByLabelText(\"First!\")).toBeInTheDocument();\r\n\r\n // FR defaults preserved (the regression class this test guards)\r\n expect(screen.getByLabelText(\"Page précédente\")).toBeInTheDocument();\r\n expect(screen.getByLabelText(\"Page suivante\")).toBeInTheDocument();\r\n expect(screen.getByLabelText(\"Dernière page\")).toBeInTheDocument();\r\n });\r\n\r\n test(\"partial top-level labels merge onto FR defaults\", () => {\r\n render(\r\n <DataGrid\r\n rows={baseRows}\r\n columns={[{ key: \"id\", header: \"ID\" }]}\r\n pagination={{ page: 1, pageSize: 25, total: 100, onPageChange: () => {} }}\r\n onSelectionChange={() => {}}\r\n labels={{\r\n tableAria: \"My table\",\r\n // selectAll, selectRow, pagination NOT provided — defaults stand.\r\n }}\r\n />\r\n );\r\n\r\n expect(screen.getByLabelText(\"My table\")).toBeInTheDocument();\r\n // Default selectAll FR survives\r\n expect(screen.getByLabelText(\"Tout sélectionner\")).toBeInTheDocument();\r\n // Default pagination FR survives\r\n expect(screen.getByLabelText(\"Première page\")).toBeInTheDocument();\r\n });\r\n\r\n test(\"legacy ariaLabel prop wins over labels.tableAria (back-compat contract)\", () => {\r\n render(\r\n <DataGrid\r\n rows={baseRows}\r\n columns={[{ key: \"id\", header: \"ID\" }]}\r\n pagination={{ page: 1, pageSize: 25, total: 100, onPageChange: () => {} }}\r\n ariaLabel=\"Legacy wins\"\r\n labels={{ tableAria: \"New label\" }}\r\n />\r\n );\r\n\r\n expect(screen.getByLabelText(\"Legacy wins\")).toBeInTheDocument();\r\n // The labels.tableAria value should NOT appear\r\n expect(screen.queryByLabelText(\"New label\")).not.toBeInTheDocument();\r\n });\r\n\r\n test(\"rangeOf custom function reaches the rendered range text\", () => {\r\n render(\r\n <DataGrid\r\n rows={baseRows}\r\n columns={[{ key: \"id\", header: \"ID\" }]}\r\n pagination={{ page: 1, pageSize: 25, total: 100, onPageChange: () => {} }}\r\n labels={{\r\n pagination: {\r\n rangeOf: (from, to, total) => `Showing ${from} to ${to} of ${total}`,\r\n },\r\n }}\r\n />\r\n );\r\n\r\n expect(screen.getByText(/Showing 1 to 25 of 100/)).toBeInTheDocument();\r\n });\r\n});\r\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -52,10 +52,43 @@ import { dateToMoment, momentToDate } from "./_pickerShared";
|
|
|
52
52
|
* the picker dialog also lets the user choose hours/minutes within
|
|
53
53
|
* the resulting day.
|
|
54
54
|
*
|
|
55
|
-
* A11y (DP-1):
|
|
56
|
-
* aria-
|
|
57
|
-
*
|
|
58
|
-
*
|
|
55
|
+
* A11y (DP-1) : when used inside <Field> the injected id + aria-describedby /
|
|
56
|
+
* aria-invalid / aria-required are intercepted and routed onto the field
|
|
57
|
+
* <input> via slotProps.htmlInput (MUI X does not forward top-level aria/id to
|
|
58
|
+
* the input). Explicit `inputProps` still win.
|
|
59
|
+
*
|
|
60
|
+
* ── KNOWN A11Y DEFECT, NOT FIXED HERE (opened as G-F, deferred) ────
|
|
61
|
+
*
|
|
62
|
+
* DP-1's routing lands on the value <input>, and MUI X v9 marks that input
|
|
63
|
+
* `aria-hidden="true"`. The visible field is a `role="group"` of one
|
|
64
|
+
* `role="spinbutton"` per section (Year, Month, Day, Hours, Minutes).
|
|
65
|
+
*
|
|
66
|
+
* So all four association attributes are INERT, on every date field of the
|
|
67
|
+
* platform, and none of it is visible on screen:
|
|
68
|
+
* — the `<label htmlFor>` names nothing: a screen reader announces « Year »,
|
|
69
|
+
* « Month », « Hours » without ever saying of WHAT (R-I.1) ;
|
|
70
|
+
* — a hint or an error message rendered by Field is never announced
|
|
71
|
+
* (R-I.3, R-4.1) ;
|
|
72
|
+
* — a required field does not report itself as required (R-I.2).
|
|
73
|
+
*
|
|
74
|
+
* Found while writing the tests for gap G-E: the DOM dump showed `role="group"`
|
|
75
|
+
* with `Name ""` beside a correctly wired `<label for>`.
|
|
76
|
+
*
|
|
77
|
+
* TWO ATTEMPTS FAILED, and both are worth recording so the next person does
|
|
78
|
+
* not repeat them:
|
|
79
|
+
* 1. routing the attrs through `slotProps.textField.InputProps` — accepted
|
|
80
|
+
* by the types, lands nowhere. `InputProps` is not the v9 anchor for the
|
|
81
|
+
* group element.
|
|
82
|
+
* 2. switching the Formik bridge to `Field`'s `group` mode — `Field` does
|
|
83
|
+
* emit `aria-labelledby` (verified in its source), but with (1) failing
|
|
84
|
+
* nothing carries it to the group, AND group mode drops the `<label
|
|
85
|
+
* for>`, so the label-click-to-focus is lost for no gain. A net
|
|
86
|
+
* regression.
|
|
87
|
+
*
|
|
88
|
+
* The fix needs the real v9 anchor for the `PickersInputBase` root, and it
|
|
89
|
+
* touches DatePicker AND DateTimePicker, hence every date field in the
|
|
90
|
+
* portfolio. It belongs to a pass with time to investigate the slot chain —
|
|
91
|
+
* Batch 10 (accessibility certification), not a side effect of G-E.
|
|
59
92
|
*
|
|
60
93
|
* MUI X v9 surface notes (migrated from v5 in Batch 4):
|
|
61
94
|
* See DatePicker.tsx for the same rationale — renderInput +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DateTimePicker.js","names":["React","LocalizationProvider","DateTimePicker","MuiDateTimePicker","AdapterMoment","dateToMoment","momentToDate","jsx","_jsx","CHARTER_FORMAT","forwardRef","_ref","ref","value","onChange","onBlur","minDate","maxDate","_ref$disablePast","disablePast","_ref$disableFuture","disableFuture","_ref$disabled","disabled","_ref$readOnly","readOnly","error","_ref$placeholder","placeholder","_ref$fullWidth","fullWidth","_ref$ampm","ampm","_ref$minutesStep","minutesStep","inputProps","_ref$adapterLocale","adapterLocale","sx","rest","_objectWithoutProperties","_excluded","_ref2","fieldId","id","ariaDescribedby","ariaInvalid","ariaRequired","ariaLabelledby","domRest","_excluded2","handleChange","next","slotProps","useMemo","textField","_objectSpread","size","fontVariantNumeric","htmlInput","minDateM","maxDateM","dateAdapter","children","format","DATETIME_FORMAT_CHARTER"],"sources":["../../../../src/lib/v2/components/DateTimePicker.tsx"],"sourcesContent":["import * as React from \"react\";\r\nimport { LocalizationProvider } from \"@mui/x-date-pickers/LocalizationProvider\";\r\nimport { DateTimePicker as MuiDateTimePicker } from \"@mui/x-date-pickers/DateTimePicker\";\r\nimport type { DateTimePickerProps as MuiDateTimePickerProps } from \"@mui/x-date-pickers/DateTimePicker\";\r\nimport { AdapterMoment } from \"@mui/x-date-pickers/AdapterMoment\";\r\nimport type { Moment } from \"moment\";\r\nimport \"moment/locale/fr\";\r\nimport type { SxProps, Theme } from \"@mui/material/styles\";\r\nimport { dateToMoment, momentToDate } from \"./_pickerShared\";\r\n\r\n/**\r\n * DateTimePicker — Portnet UI v2\r\n *\r\n * Extends the DatePicker family (Charter §03) for date+time-of-day\r\n * workflows: LC validity to the minute, audit-log timestamps,\r\n * scheduled exports, deadline pickers.\r\n *\r\n * <Field label=\"Date d'échéance\">\r\n * <DateTimePicker\r\n * value={deadline}\r\n * onChange={setDeadline}\r\n * minDate={new Date()}\r\n * />\r\n * </Field>\r\n *\r\n * Value contract:\r\n * Same as DatePicker — value/onChange exchange Date | null. Invalid\r\n * dates are coerced to null. Hosts never receive Invalid Date.\r\n *\r\n * Locale:\r\n * adapterLocale defaults to \"fr\". Hosts using other locales must\r\n * import the matching moment locale data themselves:\r\n *\r\n * import \"moment/locale/ar\";\r\n * <DateTimePicker adapterLocale=\"ar\" />\r\n *\r\n * Format:\r\n * Display + parse: AAAA-MM-JJ HH:MM (24h). MUI X v6+ derives the\r\n * input mask from this format string automatically.\r\n * ampm={true} switches to 12h with AM/PM marker — opt-in for hosts\r\n * targeting English-speaking users.\r\n *\r\n * Constraints:\r\n * minDate / maxDate accept Date objects. They bound the DATE part;\r\n * the picker dialog also lets the user choose hours/minutes within\r\n * the resulting day.\r\n *\r\n * A11y (DP-1): as with DatePicker, when used inside <Field> the injected id +\r\n * aria-describedby / aria-invalid / aria-required are intercepted and routed\r\n * onto the field <input> via slotProps.htmlInput (MUI X does not forward\r\n * top-level aria/id to the input). Explicit `inputProps` still win.\r\n *\r\n * MUI X v9 surface notes (migrated from v5 in Batch 4):\r\n * See DatePicker.tsx for the same rationale — renderInput +\r\n * inputFormat + mask removed in favor of the unified slots /\r\n * slotProps + format API. The textField slot is the canonical\r\n * input customisation entry point; slotProps.htmlInput is the v9\r\n * landing zone for what used to be TextField's inputProps.\r\n */\r\n\r\nconst CHARTER_FORMAT = \"YYYY-MM-DD HH:mm\";\r\n\r\nexport interface DateTimePickerProps\r\n extends Omit<\r\n MuiDateTimePickerProps,\r\n \"value\" | \"onChange\" | \"minDate\" | \"maxDate\" | \"slots\" | \"slotProps\" | \"sx\" | \"ref\"\r\n > {\r\n value?: Date | null | undefined;\r\n onChange?: ((value: Date | null) => void) | undefined;\r\n onBlur?: React.FocusEventHandler<HTMLInputElement | HTMLTextAreaElement> | undefined;\r\n minDate?: Date | null | undefined;\r\n maxDate?: Date | null | undefined;\r\n error?: boolean | undefined;\r\n placeholder?: string | undefined;\r\n fullWidth?: boolean | undefined;\r\n inputProps?: React.InputHTMLAttributes<HTMLInputElement> | undefined;\r\n adapterLocale?: string | undefined;\r\n sx?: SxProps<Theme> | undefined;\r\n}\r\n\r\nconst DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(function DateTimePicker(\r\n {\r\n value,\r\n onChange,\r\n onBlur,\r\n minDate,\r\n maxDate,\r\n disablePast = false,\r\n disableFuture = false,\r\n disabled = false,\r\n readOnly = false,\r\n error,\r\n placeholder = \"AAAA-MM-JJ HH:MM\",\r\n fullWidth = true,\r\n ampm = false,\r\n minutesStep = 1,\r\n inputProps,\r\n adapterLocale = \"fr\",\r\n sx,\r\n ...rest\r\n },\r\n ref,\r\n) {\r\n // DP-1 — see DatePicker.tsx. Field cloneElement-injects the label + hint/error\r\n // association props onto this child; MUI X won't forward top-level aria/id to\r\n // the field input, so intercept them (cast — not part of the picker props) and\r\n // route them onto the <input> via slotProps.htmlInput below. Everything else\r\n // stays in domRest for MuiDateTimePicker.\r\n const {\r\n id: fieldId,\r\n \"aria-describedby\": ariaDescribedby,\r\n \"aria-invalid\": ariaInvalid,\r\n \"aria-required\": ariaRequired,\r\n \"aria-labelledby\": ariaLabelledby,\r\n ...domRest\r\n } = rest as typeof rest & {\r\n id?: string;\r\n \"aria-describedby\"?: string;\r\n \"aria-invalid\"?: boolean | \"true\" | \"false\" | \"grammar\" | \"spelling\";\r\n \"aria-required\"?: boolean | \"true\" | \"false\";\r\n \"aria-labelledby\"?: string;\r\n };\r\n\r\n const handleChange = (next: Moment | null) => {\r\n if (!onChange) return;\r\n onChange(momentToDate(next));\r\n };\r\n\r\n // MUI X v9 — see DatePicker.tsx for the slotProps.textField +\r\n // nested slotProps.htmlInput rationale. htmlInput is also the DP-1\r\n // landing zone for the Field-injected id + aria-* so the label /\r\n // hint / error associate with the date-time input.\r\n const slotProps = React.useMemo<NonNullable<MuiDateTimePickerProps[\"slotProps\"]>>(\r\n () => ({\r\n textField: {\r\n ref,\r\n error: !!error,\r\n fullWidth,\r\n size: \"small\",\r\n // onBlur: the textField slot types this as FocusEventHandler<HTMLDivElement>;\r\n // our public onBlur is input-based. Cast (type-only) and include only\r\n // when defined so no explicit undefined reaches the slot.\r\n ...(onBlur ? { onBlur: onBlur as unknown as React.FocusEventHandler<HTMLDivElement> } : {}),\r\n sx: {\r\n // R-T.3 — tabular-nums for vertical alignment in tables/forms.\r\n \"& .MuiOutlinedInput-input\": { fontVariantNumeric: \"tabular-nums\" },\r\n ...sx,\r\n },\r\n slotProps: {\r\n htmlInput: {\r\n placeholder,\r\n // DP-1 — Field-injected association attrs land on the <input>.\r\n ...(fieldId != null ? { id: fieldId } : {}),\r\n ...(ariaDescribedby != null\r\n ? { \"aria-describedby\": ariaDescribedby }\r\n : {}),\r\n ...(ariaInvalid != null ? { \"aria-invalid\": ariaInvalid } : {}),\r\n ...(ariaRequired != null ? { \"aria-required\": ariaRequired } : {}),\r\n ...(ariaLabelledby != null\r\n ? { \"aria-labelledby\": ariaLabelledby }\r\n : {}),\r\n // Explicit host inputProps win over the injected defaults.\r\n ...inputProps,\r\n },\r\n },\r\n },\r\n }),\r\n [\r\n ref,\r\n error,\r\n fullWidth,\r\n onBlur,\r\n sx,\r\n placeholder,\r\n inputProps,\r\n fieldId,\r\n ariaDescribedby,\r\n ariaInvalid,\r\n ariaRequired,\r\n ariaLabelledby,\r\n ],\r\n );\r\n\r\n // exactOptionalPropertyTypes: MUI's minDate/maxDate are `Moment` (not\r\n // `Moment | undefined`), so spread the key only when a bound is set\r\n // rather than passing an explicit `undefined`.\r\n const minDateM = minDate ? dateToMoment(minDate) : null;\r\n const maxDateM = maxDate ? dateToMoment(maxDate) : null;\r\n\r\n return (\r\n <LocalizationProvider dateAdapter={AdapterMoment} adapterLocale={adapterLocale}>\r\n <MuiDateTimePicker\r\n value={dateToMoment(value)}\r\n onChange={handleChange}\r\n {...(minDateM ? { minDate: minDateM } : {})}\r\n {...(maxDateM ? { maxDate: maxDateM } : {})}\r\n disablePast={disablePast}\r\n disableFuture={disableFuture}\r\n disabled={disabled}\r\n readOnly={readOnly}\r\n ampm={ampm}\r\n minutesStep={minutesStep}\r\n format={CHARTER_FORMAT}\r\n slotProps={slotProps}\r\n {...domRest}\r\n />\r\n </LocalizationProvider>\r\n );\r\n});\r\n\r\nexport const DATETIME_FORMAT_CHARTER = CHARTER_FORMAT;\r\n\r\nexport default DateTimePicker;\r\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"DateTimePicker.js","names":["React","LocalizationProvider","DateTimePicker","MuiDateTimePicker","AdapterMoment","dateToMoment","momentToDate","jsx","_jsx","CHARTER_FORMAT","forwardRef","_ref","ref","value","onChange","onBlur","minDate","maxDate","_ref$disablePast","disablePast","_ref$disableFuture","disableFuture","_ref$disabled","disabled","_ref$readOnly","readOnly","error","_ref$placeholder","placeholder","_ref$fullWidth","fullWidth","_ref$ampm","ampm","_ref$minutesStep","minutesStep","inputProps","_ref$adapterLocale","adapterLocale","sx","rest","_objectWithoutProperties","_excluded","_ref2","fieldId","id","ariaDescribedby","ariaInvalid","ariaRequired","ariaLabelledby","domRest","_excluded2","handleChange","next","slotProps","useMemo","textField","_objectSpread","size","fontVariantNumeric","htmlInput","minDateM","maxDateM","dateAdapter","children","format","DATETIME_FORMAT_CHARTER"],"sources":["../../../../src/lib/v2/components/DateTimePicker.tsx"],"sourcesContent":["import * as React from \"react\";\r\nimport { LocalizationProvider } from \"@mui/x-date-pickers/LocalizationProvider\";\r\nimport { DateTimePicker as MuiDateTimePicker } from \"@mui/x-date-pickers/DateTimePicker\";\r\nimport type { DateTimePickerProps as MuiDateTimePickerProps } from \"@mui/x-date-pickers/DateTimePicker\";\r\nimport { AdapterMoment } from \"@mui/x-date-pickers/AdapterMoment\";\r\nimport type { Moment } from \"moment\";\r\nimport \"moment/locale/fr\";\r\nimport type { SxProps, Theme } from \"@mui/material/styles\";\r\nimport { dateToMoment, momentToDate } from \"./_pickerShared\";\r\n\r\n/**\r\n * DateTimePicker — Portnet UI v2\r\n *\r\n * Extends the DatePicker family (Charter §03) for date+time-of-day\r\n * workflows: LC validity to the minute, audit-log timestamps,\r\n * scheduled exports, deadline pickers.\r\n *\r\n * <Field label=\"Date d'échéance\">\r\n * <DateTimePicker\r\n * value={deadline}\r\n * onChange={setDeadline}\r\n * minDate={new Date()}\r\n * />\r\n * </Field>\r\n *\r\n * Value contract:\r\n * Same as DatePicker — value/onChange exchange Date | null. Invalid\r\n * dates are coerced to null. Hosts never receive Invalid Date.\r\n *\r\n * Locale:\r\n * adapterLocale defaults to \"fr\". Hosts using other locales must\r\n * import the matching moment locale data themselves:\r\n *\r\n * import \"moment/locale/ar\";\r\n * <DateTimePicker adapterLocale=\"ar\" />\r\n *\r\n * Format:\r\n * Display + parse: AAAA-MM-JJ HH:MM (24h). MUI X v6+ derives the\r\n * input mask from this format string automatically.\r\n * ampm={true} switches to 12h with AM/PM marker — opt-in for hosts\r\n * targeting English-speaking users.\r\n *\r\n * Constraints:\r\n * minDate / maxDate accept Date objects. They bound the DATE part;\r\n * the picker dialog also lets the user choose hours/minutes within\r\n * the resulting day.\r\n *\r\n * A11y (DP-1) : when used inside <Field> the injected id + aria-describedby /\r\n * aria-invalid / aria-required are intercepted and routed onto the field\r\n * <input> via slotProps.htmlInput (MUI X does not forward top-level aria/id to\r\n * the input). Explicit `inputProps` still win.\r\n *\r\n * ── KNOWN A11Y DEFECT, NOT FIXED HERE (opened as G-F, deferred) ────\r\n *\r\n * DP-1's routing lands on the value <input>, and MUI X v9 marks that input\r\n * `aria-hidden=\"true\"`. The visible field is a `role=\"group\"` of one\r\n * `role=\"spinbutton\"` per section (Year, Month, Day, Hours, Minutes).\r\n *\r\n * So all four association attributes are INERT, on every date field of the\r\n * platform, and none of it is visible on screen:\r\n * — the `<label htmlFor>` names nothing: a screen reader announces « Year »,\r\n * « Month », « Hours » without ever saying of WHAT (R-I.1) ;\r\n * — a hint or an error message rendered by Field is never announced\r\n * (R-I.3, R-4.1) ;\r\n * — a required field does not report itself as required (R-I.2).\r\n *\r\n * Found while writing the tests for gap G-E: the DOM dump showed `role=\"group\"`\r\n * with `Name \"\"` beside a correctly wired `<label for>`.\r\n *\r\n * TWO ATTEMPTS FAILED, and both are worth recording so the next person does\r\n * not repeat them:\r\n * 1. routing the attrs through `slotProps.textField.InputProps` — accepted\r\n * by the types, lands nowhere. `InputProps` is not the v9 anchor for the\r\n * group element.\r\n * 2. switching the Formik bridge to `Field`'s `group` mode — `Field` does\r\n * emit `aria-labelledby` (verified in its source), but with (1) failing\r\n * nothing carries it to the group, AND group mode drops the `<label\r\n * for>`, so the label-click-to-focus is lost for no gain. A net\r\n * regression.\r\n *\r\n * The fix needs the real v9 anchor for the `PickersInputBase` root, and it\r\n * touches DatePicker AND DateTimePicker, hence every date field in the\r\n * portfolio. It belongs to a pass with time to investigate the slot chain —\r\n * Batch 10 (accessibility certification), not a side effect of G-E.\r\n *\r\n * MUI X v9 surface notes (migrated from v5 in Batch 4):\r\n * See DatePicker.tsx for the same rationale — renderInput +\r\n * inputFormat + mask removed in favor of the unified slots /\r\n * slotProps + format API. The textField slot is the canonical\r\n * input customisation entry point; slotProps.htmlInput is the v9\r\n * landing zone for what used to be TextField's inputProps.\r\n */\r\n\r\nconst CHARTER_FORMAT = \"YYYY-MM-DD HH:mm\";\r\n\r\nexport interface DateTimePickerProps\r\n extends Omit<\r\n MuiDateTimePickerProps,\r\n \"value\" | \"onChange\" | \"minDate\" | \"maxDate\" | \"slots\" | \"slotProps\" | \"sx\" | \"ref\"\r\n > {\r\n value?: Date | null | undefined;\r\n onChange?: ((value: Date | null) => void) | undefined;\r\n onBlur?: React.FocusEventHandler<HTMLInputElement | HTMLTextAreaElement> | undefined;\r\n minDate?: Date | null | undefined;\r\n maxDate?: Date | null | undefined;\r\n error?: boolean | undefined;\r\n placeholder?: string | undefined;\r\n fullWidth?: boolean | undefined;\r\n inputProps?: React.InputHTMLAttributes<HTMLInputElement> | undefined;\r\n adapterLocale?: string | undefined;\r\n sx?: SxProps<Theme> | undefined;\r\n}\r\n\r\nconst DateTimePicker = React.forwardRef<HTMLDivElement, DateTimePickerProps>(function DateTimePicker(\r\n {\r\n value,\r\n onChange,\r\n onBlur,\r\n minDate,\r\n maxDate,\r\n disablePast = false,\r\n disableFuture = false,\r\n disabled = false,\r\n readOnly = false,\r\n error,\r\n placeholder = \"AAAA-MM-JJ HH:MM\",\r\n fullWidth = true,\r\n ampm = false,\r\n minutesStep = 1,\r\n inputProps,\r\n adapterLocale = \"fr\",\r\n sx,\r\n ...rest\r\n },\r\n ref,\r\n) {\r\n // DP-1 — see DatePicker.tsx. Field cloneElement-injects the label + hint/error\r\n // association props onto this child; MUI X won't forward top-level aria/id to\r\n // the field input, so intercept them (cast — not part of the picker props) and\r\n // route them onto the <input> via slotProps.htmlInput below. Everything else\r\n // stays in domRest for MuiDateTimePicker.\r\n const {\r\n id: fieldId,\r\n \"aria-describedby\": ariaDescribedby,\r\n \"aria-invalid\": ariaInvalid,\r\n \"aria-required\": ariaRequired,\r\n \"aria-labelledby\": ariaLabelledby,\r\n ...domRest\r\n } = rest as typeof rest & {\r\n id?: string;\r\n \"aria-describedby\"?: string;\r\n \"aria-invalid\"?: boolean | \"true\" | \"false\" | \"grammar\" | \"spelling\";\r\n \"aria-required\"?: boolean | \"true\" | \"false\";\r\n \"aria-labelledby\"?: string;\r\n };\r\n\r\n const handleChange = (next: Moment | null) => {\r\n if (!onChange) return;\r\n onChange(momentToDate(next));\r\n };\r\n\r\n // MUI X v9 — see DatePicker.tsx for the slotProps.textField +\r\n // nested slotProps.htmlInput rationale. htmlInput is also the DP-1\r\n // landing zone for the Field-injected id + aria-* so the label /\r\n // hint / error associate with the date-time input.\r\n const slotProps = React.useMemo<NonNullable<MuiDateTimePickerProps[\"slotProps\"]>>(\r\n () => ({\r\n textField: {\r\n ref,\r\n error: !!error,\r\n fullWidth,\r\n size: \"small\",\r\n // onBlur: the textField slot types this as FocusEventHandler<HTMLDivElement>;\r\n // our public onBlur is input-based. Cast (type-only) and include only\r\n // when defined so no explicit undefined reaches the slot.\r\n ...(onBlur ? { onBlur: onBlur as unknown as React.FocusEventHandler<HTMLDivElement> } : {}),\r\n sx: {\r\n // R-T.3 — tabular-nums for vertical alignment in tables/forms.\r\n \"& .MuiOutlinedInput-input\": { fontVariantNumeric: \"tabular-nums\" },\r\n ...sx,\r\n },\r\n slotProps: {\r\n htmlInput: {\r\n placeholder,\r\n // DP-1 — Field-injected association attrs land on the <input>.\r\n ...(fieldId != null ? { id: fieldId } : {}),\r\n ...(ariaDescribedby != null\r\n ? { \"aria-describedby\": ariaDescribedby }\r\n : {}),\r\n ...(ariaInvalid != null ? { \"aria-invalid\": ariaInvalid } : {}),\r\n ...(ariaRequired != null ? { \"aria-required\": ariaRequired } : {}),\r\n ...(ariaLabelledby != null\r\n ? { \"aria-labelledby\": ariaLabelledby }\r\n : {}),\r\n // Explicit host inputProps win over the injected defaults.\r\n ...inputProps,\r\n },\r\n },\r\n },\r\n }),\r\n [\r\n ref,\r\n error,\r\n fullWidth,\r\n onBlur,\r\n sx,\r\n placeholder,\r\n inputProps,\r\n fieldId,\r\n ariaDescribedby,\r\n ariaInvalid,\r\n ariaRequired,\r\n ariaLabelledby,\r\n ],\r\n );\r\n\r\n // exactOptionalPropertyTypes: MUI's minDate/maxDate are `Moment` (not\r\n // `Moment | undefined`), so spread the key only when a bound is set\r\n // rather than passing an explicit `undefined`.\r\n const minDateM = minDate ? dateToMoment(minDate) : null;\r\n const maxDateM = maxDate ? dateToMoment(maxDate) : null;\r\n\r\n return (\r\n <LocalizationProvider dateAdapter={AdapterMoment} adapterLocale={adapterLocale}>\r\n <MuiDateTimePicker\r\n value={dateToMoment(value)}\r\n onChange={handleChange}\r\n {...(minDateM ? { minDate: minDateM } : {})}\r\n {...(maxDateM ? { maxDate: maxDateM } : {})}\r\n disablePast={disablePast}\r\n disableFuture={disableFuture}\r\n disabled={disabled}\r\n readOnly={readOnly}\r\n ampm={ampm}\r\n minutesStep={minutesStep}\r\n format={CHARTER_FORMAT}\r\n slotProps={slotProps}\r\n {...domRest}\r\n />\r\n </LocalizationProvider>\r\n );\r\n});\r\n\r\nexport const DATETIME_FORMAT_CHARTER = CHARTER_FORMAT;\r\n\r\nexport default DateTimePicker;\r\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -46,8 +46,64 @@ import FieldError from "./FieldError";
|
|
|
46
46
|
* <Field label="Genre" group error={errors.genre}>
|
|
47
47
|
* <RadioGroup … />
|
|
48
48
|
* </Field>
|
|
49
|
+
*
|
|
50
|
+
* ── LARGEUR SEMANTIQUE (gap G-H) ──────────────────────────────
|
|
51
|
+
*
|
|
52
|
+
* `width` rattache le champ a l'un des cinq gabarits imposes par la section
|
|
53
|
+
* « Form Field Width & Layout Standards » des Platform Mandatory UX Patterns.
|
|
54
|
+
* Le standard y interdit explicitement les « arbitrary input field widths » et
|
|
55
|
+
* les « jagged alignment fields within forms », et impose de mapper chaque
|
|
56
|
+
* champ a un gabarit d'apres sa contrainte de CARACTERES :
|
|
57
|
+
*
|
|
58
|
+
* xs 1 a 5 caracteres code postal, CVV, age
|
|
59
|
+
* s 6 a 15 telephone, montant, date, identifiant court
|
|
60
|
+
* m 16 a 60 nom, courriel, ville, liste simple
|
|
61
|
+
* l 61 a 256 adresse, recherche complexe, longue liste
|
|
62
|
+
* xl 257 et plus zone de texte, description, editeur
|
|
63
|
+
*
|
|
64
|
+
* POURQUOI SUR `Field` ET NON SUR CHAQUE CONTROLE. Le gabarit est une
|
|
65
|
+
* contrainte de MISE EN PAGE, pas une propriete de l'input : c'est la boite du
|
|
66
|
+
* champ - libelle, controle, indication, erreur - qui doit etre bornee, sinon
|
|
67
|
+
* le message d'erreur s'etale sur toute la cellule pendant que l'input est
|
|
68
|
+
* borne. `Field` est le seul endroit qui voit cette boite entiere.
|
|
69
|
+
*
|
|
70
|
+
* POURQUOI UN PLAFOND ET NON UNE LARGEUR FIXE. Le standard exige aussi que les
|
|
71
|
+
* champs soient empiles « 100% grid width » sur mobile, et que des champs
|
|
72
|
+
* heterogenes s'alignent sur des fractions de colonnes. Un `maxWidth`
|
|
73
|
+
* n'est donc pose qu'a partir de `sm` : la grille continue de decider des
|
|
74
|
+
* COLONNES, le gabarit borne le champ DANS sa cellule. Les deux regles
|
|
75
|
+
* coexistent au lieu de se contredire.
|
|
76
|
+
*
|
|
77
|
+
* >>> LES CINQ VALEURS SONT UNE DECISION DE DESIGN, PAS UNE LECTURE. Le
|
|
78
|
+
* >>> standard donne des FOURCHETTES (« ~80px to 120px max »). J'ai retenu le
|
|
79
|
+
* >>> HAUT de chaque fourchette, ces largeurs etant des plafonds : prendre le
|
|
80
|
+
* >>> bas tronquerait des valeurs qui tiennent dans la contrainte de
|
|
81
|
+
* >>> caracteres annoncee. A confirmer par l'equipe design ; les cinq nombres
|
|
82
|
+
* >>> sont exposes en variables CSS pour etre revus sans toucher au code. <<<
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Gabarits de largeur semantique (G-H). Voir la note en tete de fichier.
|
|
87
|
+
*
|
|
88
|
+
* Les valeurs passent par des variables CSS pour que l'equipe design puisse
|
|
89
|
+
* les revoir sans modification de code. Les replis sont le HAUT de chaque
|
|
90
|
+
* fourchette du standard, ces largeurs etant des plafonds.
|
|
49
91
|
*/
|
|
50
92
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
93
|
+
/**
|
|
94
|
+
* Exporte pour etre TESTABLE : le plafond ne s'applique qu'a partir de `sm`,
|
|
95
|
+
* donc via une requete media qu'`emotion` injecte dans la feuille de style et
|
|
96
|
+
* que jsdom ne resout pas. Un test de rendu ne peut donc pas lire la largeur
|
|
97
|
+
* effective ; il peut en revanche epingler les cinq valeurs, ce qui est le
|
|
98
|
+
* point sensible (elles sont une decision de design, voir la note ci-dessus).
|
|
99
|
+
*/
|
|
100
|
+
export var FIELD_MAX_WIDTH = {
|
|
101
|
+
xs: "var(--field-w-xs, 120px)",
|
|
102
|
+
s: "var(--field-w-s, 200px)",
|
|
103
|
+
m: "var(--field-w-m, 400px)",
|
|
104
|
+
l: "var(--field-w-l, 600px)",
|
|
105
|
+
xl: "100%"
|
|
106
|
+
};
|
|
51
107
|
function Field(_ref) {
|
|
52
108
|
var label = _ref.label,
|
|
53
109
|
_ref$required = _ref.required,
|
|
@@ -60,6 +116,7 @@ function Field(_ref) {
|
|
|
60
116
|
disabled = _ref.disabled,
|
|
61
117
|
_ref$group = _ref.group,
|
|
62
118
|
group = _ref$group === void 0 ? false : _ref$group,
|
|
119
|
+
width = _ref.width,
|
|
63
120
|
children = _ref.children,
|
|
64
121
|
helpAdornment = _ref.helpAdornment,
|
|
65
122
|
sx = _ref.sx,
|
|
@@ -148,7 +205,12 @@ function Field(_ref) {
|
|
|
148
205
|
disabled: disabled,
|
|
149
206
|
fullWidth: fullWidth,
|
|
150
207
|
className: className,
|
|
151
|
-
sx:
|
|
208
|
+
sx: _objectSpread(_objectSpread({}, width ? {
|
|
209
|
+
maxWidth: {
|
|
210
|
+
xs: "100%",
|
|
211
|
+
sm: FIELD_MAX_WIDTH[width]
|
|
212
|
+
}
|
|
213
|
+
} : {}), sx),
|
|
152
214
|
children: [label && (group ?
|
|
153
215
|
/*#__PURE__*/
|
|
154
216
|
// Group mode: render as a <span> carrying an id (no <label for>),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Field.js","names":["React","FormControl","FormLabel","FormHelperText","Box","FieldError","jsx","_jsx","Fragment","_Fragment","jsxs","_jsxs","Field","_ref","label","_ref$required","required","hint","error","htmlFor","_ref$fullWidth","fullWidth","disabled","_ref$group","group","children","helpAdornment","sx","className","reactId","useId","auto","concat","replace","inputId","hintId","errorId","labelId","hasError","child","Children","only","childProps","props","describedBy","filter","Boolean","join","undefined","labelledBy","enhanced","cloneElement","_objectSpread","id","helperResetSx","marginInlineStart","fontSize","LABEL_SX","display","alignItems","gap","fontWeight","color","marginBottom","labelContent","component","marginTop"],"sources":["../../../../src/lib/v2/components/Field.tsx"],"sourcesContent":["import * as React from \"react\";\r\nimport FormControl from \"@mui/material/FormControl\";\r\nimport FormLabel from \"@mui/material/FormLabel\";\r\nimport FormHelperText from \"@mui/material/FormHelperText\";\r\nimport Box from \"@mui/material/Box\";\r\nimport type { SxProps, Theme } from \"@mui/material/styles\";\r\nimport FieldError from \"./FieldError\";\r\n\r\n/**\r\n * Field — Portnet UI v2\r\n *\r\n * Charter §03 · Component \"Input · Field\".\r\n * Universal wrapper providing label / required marker / hint / error / a11y bindings\r\n * for any form control (Input, Select, Textarea, Autocomplete, Combobox, …).\r\n *\r\n * Strict rules enforced:\r\n * R-I.1 — Label is associated to the control via `htmlFor` / `id`.\r\n * R-I.2 — Required adds the asterisk + `aria-required`.\r\n * R-I.3 — Error: `aria-invalid` + `aria-describedby` linking the message.\r\n * Field MERGES with the child's existing `aria-describedby` so a\r\n * consumer-supplied external help id is never lost. The message\r\n * itself is rendered by the shared `FieldError` primitive, which\r\n * carries the R-4.1 MUST icon (gap G-18) — previously this component\r\n * rendered a bare red `FormHelperText` with no icon, so the canonical\r\n * field wrapper did not satisfy a MUST and every module inherited the\r\n * gap on every form.\r\n * R-I.4 — Validation in `blur`, not `change` (caller's responsibility).\r\n * R-I.6 — Hint visible above error; never both at once.\r\n *\r\n * <Field label=\"Référence\" required hint=\"Format AAAA-IM-NNNNN\" error={errors.ref}>\r\n * <Input value={value} onChange={onChange} />\r\n * </Field>\r\n *\r\n * Grouping controls (RadioGroup / CheckboxGroup) — pass `group`:\r\n * A native `<label htmlFor>` cannot name an ARIA group (only labelable\r\n * elements can be `for`-targeted), so in `group` mode the label is rendered\r\n * as a <span> with an id and the child group is associated via\r\n * `aria-labelledby` instead. The child must forward `aria-labelledby`\r\n * (and `aria-describedby`) onto its `role=\"group\"`/`\"radiogroup\"` element.\r\n *\r\n * <Field label=\"Genre\" group error={errors.genre}>\r\n * <RadioGroup … />\r\n * </Field>\r\n */\r\nexport interface FieldProps {\r\n label?: React.ReactNode | undefined;\r\n required?: boolean | undefined;\r\n hint?: React.ReactNode | undefined;\r\n error?: React.ReactNode | undefined;\r\n htmlFor?: string | undefined;\r\n fullWidth?: boolean | undefined;\r\n disabled?: boolean | undefined;\r\n /**\r\n * The wrapped child is an ARIA group (RadioGroup / CheckboxGroup) rather than\r\n * a single labelable control. Associates the label via `aria-labelledby`\r\n * instead of `htmlFor` / `id`.\r\n */\r\n group?: boolean | undefined;\r\n /** Optional <FieldHelp> element rendered next to the label. */\r\n helpAdornment?: React.ReactNode | undefined;\r\n /** The single form-control element to wrap. */\r\n children: React.ReactElement;\r\n sx?: SxProps<Theme> | undefined;\r\n className?: string | undefined;\r\n}\r\n\r\nfunction Field({\r\n label,\r\n required = false,\r\n hint,\r\n error,\r\n htmlFor,\r\n fullWidth = true,\r\n disabled,\r\n group = false,\r\n children,\r\n helpAdornment, // optional <FieldHelp> element\r\n sx,\r\n className,\r\n}: FieldProps) {\r\n // R-I.1 — useId provides SSR-safe, per-instance, stable IDs.\r\n // Replaces the previous module-scoped counter (`let __uid = 0`) which broke\r\n // hydration and Concurrent Mode.\r\n const reactId = React.useId();\r\n const auto = `pui-${reactId.replace(/:/g, \"\")}`;\r\n const inputId = htmlFor || auto;\r\n const hintId = `${inputId}-h`;\r\n const errorId = `${inputId}-e`;\r\n const labelId = `${inputId}-l`;\r\n const hasError = !!error;\r\n\r\n const child = React.Children.only(children);\r\n // The wrapped control is polymorphic (Input/Select/NumberInput/Combobox/…);\r\n // type only the props Field reads/merges, replacing React's `props: any`.\r\n const childProps = child.props as {\r\n id?: string;\r\n disabled?: boolean;\r\n fullWidth?: boolean;\r\n \"aria-describedby\"?: string;\r\n \"aria-labelledby\"?: string;\r\n };\r\n\r\n // R-I.3 — MERGE the child's existing aria-describedby with our hint/error\r\n // ids. cloneElement replaces props, so without an explicit merge any\r\n // consumer-supplied describedby was silently discarded.\r\n const describedBy = [\r\n childProps[\"aria-describedby\"],\r\n hint && !hasError ? hintId : null,\r\n hasError ? errorId : null,\r\n ].filter(Boolean).join(\" \") || undefined;\r\n\r\n // CC-6 — in group mode the label names the group via aria-labelledby.\r\n // Merge with any consumer-supplied labelledby so it is never lost.\r\n const labelledBy = group\r\n ? [childProps[\"aria-labelledby\"], label ? labelId : null]\r\n .filter(Boolean)\r\n .join(\" \") || undefined\r\n : undefined;\r\n\r\n const enhanced = React.cloneElement(child as React.ReactElement<Record<string, unknown>>, {\r\n id: childProps.id || inputId,\r\n \"aria-invalid\": hasError ? \"true\" : undefined,\r\n \"aria-required\": required ? \"true\" : undefined,\r\n \"aria-describedby\": describedBy,\r\n // Only in group mode — a single labelable control is named by the\r\n // htmlFor/id pair below, not by aria-labelledby.\r\n ...(group ? { \"aria-labelledby\": labelledBy } : {}),\r\n // `error` / `disabled` are broadcast by FormControl via context for MUI\r\n // inputs, but several v2 inputs (NumberInput, Combobox renderInput) are\r\n // not pure MUI children and read the props directly — keep them explicit.\r\n error: hasError,\r\n disabled: disabled || childProps.disabled,\r\n // Respect the child's explicit fullWidth when defined (e.g. a narrow\r\n // inline filter input). Only fall back to Field's value when the child\r\n // hasn't expressed an opinion.\r\n fullWidth: childProps.fullWidth !== undefined ? childProps.fullWidth : fullWidth,\r\n });\r\n\r\n // R-I.6 — RTL-safe margin reset. `marginLeft: 0` only zeroes the LTR\r\n // inline-start; in [dir=\"rtl\"] the helper picks up MUI's default on\r\n // the (now physical-right but logical-start) side and is mis-aligned.\r\n // `marginInlineStart` resets the correct edge in both directions.\r\n const helperResetSx = { marginInlineStart: 0, fontSize: \"var(--text-xs)\" };\r\n\r\n const LABEL_SX = {\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: \"var(--s-1)\",\r\n fontSize: \"var(--text-xs, 12px)\",\r\n fontWeight: \"var(--fw-semibold, 600)\",\r\n color: \"var(--fg-muted)\",\r\n marginBottom: \"var(--s-2)\",\r\n \"&.Mui-focused\": { color: \"var(--fg-muted)\" },\r\n \"&.Mui-error\": { color: \"var(--fg-muted)\" },\r\n } as const;\r\n\r\n // Shared label content (text + required marker + optional help adornment).\r\n const labelContent = (\r\n <>\r\n {label}\r\n {required && (\r\n <Box\r\n component=\"span\"\r\n aria-hidden=\"true\"\r\n sx={{ color: \"var(--danger)\", fontWeight: \"var(--fw-bold, 700)\" }}\r\n >\r\n *\r\n </Box>\r\n )}\r\n {helpAdornment}\r\n </>\r\n );\r\n\r\n return (\r\n <FormControl\r\n error={hasError}\r\n required={required}\r\n disabled={disabled}\r\n fullWidth={fullWidth}\r\n className={className}\r\n sx={sx}\r\n >\r\n {label &&\r\n (group ? (\r\n // Group mode: render as a <span> carrying an id (no <label for>),\r\n // referenced by the group's aria-labelledby.\r\n <FormLabel component=\"span\" id={labelId} required={false} sx={LABEL_SX}>\r\n {labelContent}\r\n </FormLabel>\r\n ) : (\r\n <FormLabel htmlFor={inputId} required={false /* we render the * ourselves */} sx={LABEL_SX}>\r\n {labelContent}\r\n </FormLabel>\r\n ))}\r\n\r\n {enhanced}\r\n\r\n {hint && !hasError && (\r\n <FormHelperText\r\n id={hintId}\r\n sx={{ ...helperResetSx, color: \"var(--fg-subtle)\" }}\r\n >\r\n {hint}\r\n </FormHelperText>\r\n )}\r\n\r\n {hasError && (\r\n /*\r\n * Rendered by the shared `FieldError` (gap G-18) rather than an inline\r\n * `FormHelperText`: R-4.1 MUST requires the icon, and a host with a\r\n * LIST-level error (a Formik array, an upload slot) has no single\r\n * control to wrap in a `Field` and needs the exact same renderer\r\n * standalone. One component, both paths, no drift.\r\n *\r\n * `marginTop: var(--s-1)` (4px) replaces `FormHelperText`'s hard-coded\r\n * 3px default — same optical result, on the spacing scale (R-S.1).\r\n * Dropping FormHelperText also removed a `color: … !important` that\r\n * only existed to beat MUI's own `Mui-error` colour.\r\n */\r\n <FieldError id={errorId} sx={{ marginTop: \"var(--s-1)\" }}>\r\n {error}\r\n </FieldError>\r\n )}\r\n </FormControl>\r\n );\r\n}\r\n\r\nexport default Field;\r\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"Field.js","names":["React","FormControl","FormLabel","FormHelperText","Box","FieldError","jsx","_jsx","Fragment","_Fragment","jsxs","_jsxs","FIELD_MAX_WIDTH","xs","s","m","l","xl","Field","_ref","label","_ref$required","required","hint","error","htmlFor","_ref$fullWidth","fullWidth","disabled","_ref$group","group","width","children","helpAdornment","sx","className","reactId","useId","auto","concat","replace","inputId","hintId","errorId","labelId","hasError","child","Children","only","childProps","props","describedBy","filter","Boolean","join","undefined","labelledBy","enhanced","cloneElement","_objectSpread","id","helperResetSx","marginInlineStart","fontSize","LABEL_SX","display","alignItems","gap","fontWeight","color","marginBottom","labelContent","component","maxWidth","sm","marginTop"],"sources":["../../../../src/lib/v2/components/Field.tsx"],"sourcesContent":["import * as React from \"react\";\r\nimport FormControl from \"@mui/material/FormControl\";\r\nimport FormLabel from \"@mui/material/FormLabel\";\r\nimport FormHelperText from \"@mui/material/FormHelperText\";\r\nimport Box from \"@mui/material/Box\";\r\nimport type { SxProps, Theme } from \"@mui/material/styles\";\r\nimport FieldError from \"./FieldError\";\r\n\r\n/**\r\n * Field — Portnet UI v2\r\n *\r\n * Charter §03 · Component \"Input · Field\".\r\n * Universal wrapper providing label / required marker / hint / error / a11y bindings\r\n * for any form control (Input, Select, Textarea, Autocomplete, Combobox, …).\r\n *\r\n * Strict rules enforced:\r\n * R-I.1 — Label is associated to the control via `htmlFor` / `id`.\r\n * R-I.2 — Required adds the asterisk + `aria-required`.\r\n * R-I.3 — Error: `aria-invalid` + `aria-describedby` linking the message.\r\n * Field MERGES with the child's existing `aria-describedby` so a\r\n * consumer-supplied external help id is never lost. The message\r\n * itself is rendered by the shared `FieldError` primitive, which\r\n * carries the R-4.1 MUST icon (gap G-18) — previously this component\r\n * rendered a bare red `FormHelperText` with no icon, so the canonical\r\n * field wrapper did not satisfy a MUST and every module inherited the\r\n * gap on every form.\r\n * R-I.4 — Validation in `blur`, not `change` (caller's responsibility).\r\n * R-I.6 — Hint visible above error; never both at once.\r\n *\r\n * <Field label=\"Référence\" required hint=\"Format AAAA-IM-NNNNN\" error={errors.ref}>\r\n * <Input value={value} onChange={onChange} />\r\n * </Field>\r\n *\r\n * Grouping controls (RadioGroup / CheckboxGroup) — pass `group`:\r\n * A native `<label htmlFor>` cannot name an ARIA group (only labelable\r\n * elements can be `for`-targeted), so in `group` mode the label is rendered\r\n * as a <span> with an id and the child group is associated via\r\n * `aria-labelledby` instead. The child must forward `aria-labelledby`\r\n * (and `aria-describedby`) onto its `role=\"group\"`/`\"radiogroup\"` element.\r\n *\r\n * <Field label=\"Genre\" group error={errors.genre}>\r\n * <RadioGroup … />\r\n * </Field>\r\n *\r\n * ── LARGEUR SEMANTIQUE (gap G-H) ──────────────────────────────\r\n *\r\n * `width` rattache le champ a l'un des cinq gabarits imposes par la section\r\n * « Form Field Width & Layout Standards » des Platform Mandatory UX Patterns.\r\n * Le standard y interdit explicitement les « arbitrary input field widths » et\r\n * les « jagged alignment fields within forms », et impose de mapper chaque\r\n * champ a un gabarit d'apres sa contrainte de CARACTERES :\r\n *\r\n * xs 1 a 5 caracteres code postal, CVV, age\r\n * s 6 a 15 telephone, montant, date, identifiant court\r\n * m 16 a 60 nom, courriel, ville, liste simple\r\n * l 61 a 256 adresse, recherche complexe, longue liste\r\n * xl 257 et plus zone de texte, description, editeur\r\n *\r\n * POURQUOI SUR `Field` ET NON SUR CHAQUE CONTROLE. Le gabarit est une\r\n * contrainte de MISE EN PAGE, pas une propriete de l'input : c'est la boite du\r\n * champ - libelle, controle, indication, erreur - qui doit etre bornee, sinon\r\n * le message d'erreur s'etale sur toute la cellule pendant que l'input est\r\n * borne. `Field` est le seul endroit qui voit cette boite entiere.\r\n *\r\n * POURQUOI UN PLAFOND ET NON UNE LARGEUR FIXE. Le standard exige aussi que les\r\n * champs soient empiles « 100% grid width » sur mobile, et que des champs\r\n * heterogenes s'alignent sur des fractions de colonnes. Un `maxWidth`\r\n * n'est donc pose qu'a partir de `sm` : la grille continue de decider des\r\n * COLONNES, le gabarit borne le champ DANS sa cellule. Les deux regles\r\n * coexistent au lieu de se contredire.\r\n *\r\n * >>> LES CINQ VALEURS SONT UNE DECISION DE DESIGN, PAS UNE LECTURE. Le\r\n * >>> standard donne des FOURCHETTES (« ~80px to 120px max »). J'ai retenu le\r\n * >>> HAUT de chaque fourchette, ces largeurs etant des plafonds : prendre le\r\n * >>> bas tronquerait des valeurs qui tiennent dans la contrainte de\r\n * >>> caracteres annoncee. A confirmer par l'equipe design ; les cinq nombres\r\n * >>> sont exposes en variables CSS pour etre revus sans toucher au code. <<<\r\n */\r\n\r\n/**\r\n * Gabarits de largeur semantique (G-H). Voir la note en tete de fichier.\r\n *\r\n * Les valeurs passent par des variables CSS pour que l'equipe design puisse\r\n * les revoir sans modification de code. Les replis sont le HAUT de chaque\r\n * fourchette du standard, ces largeurs etant des plafonds.\r\n */\r\nexport type FieldWidth = \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\";\r\n\r\n/**\r\n * Exporte pour etre TESTABLE : le plafond ne s'applique qu'a partir de `sm`,\r\n * donc via une requete media qu'`emotion` injecte dans la feuille de style et\r\n * que jsdom ne resout pas. Un test de rendu ne peut donc pas lire la largeur\r\n * effective ; il peut en revanche epingler les cinq valeurs, ce qui est le\r\n * point sensible (elles sont une decision de design, voir la note ci-dessus).\r\n */\r\nexport const FIELD_MAX_WIDTH: Record<FieldWidth, string> = {\r\n xs: \"var(--field-w-xs, 120px)\",\r\n s: \"var(--field-w-s, 200px)\",\r\n m: \"var(--field-w-m, 400px)\",\r\n l: \"var(--field-w-l, 600px)\",\r\n xl: \"100%\",\r\n};\r\n\r\nexport interface FieldProps {\r\n label?: React.ReactNode | undefined;\r\n required?: boolean | undefined;\r\n hint?: React.ReactNode | undefined;\r\n error?: React.ReactNode | undefined;\r\n htmlFor?: string | undefined;\r\n fullWidth?: boolean | undefined;\r\n disabled?: boolean | undefined;\r\n /**\r\n * The wrapped child is an ARIA group (RadioGroup / CheckboxGroup) rather than\r\n * a single labelable control. Associates the label via `aria-labelledby`\r\n * instead of `htmlFor` / `id`.\r\n */\r\n group?: boolean | undefined;\r\n /**\r\n * Gabarit de largeur semantique (G-H) : `xs` | `s` | `m` | `l` | `xl`,\r\n * choisi d'apres la contrainte de CARACTERES du champ. Voir la note en tete\r\n * de fichier. Omis = aucun plafond, la cellule de grille decide seule -\r\n * c'est le comportement d'avant G-H, donc rien ne change pour les appelants\r\n * existants.\r\n */\r\n width?: FieldWidth | undefined;\r\n /** Optional <FieldHelp> element rendered next to the label. */\r\n helpAdornment?: React.ReactNode | undefined;\r\n /** The single form-control element to wrap. */\r\n children: React.ReactElement;\r\n sx?: SxProps<Theme> | undefined;\r\n className?: string | undefined;\r\n}\r\n\r\nfunction Field({\r\n label,\r\n required = false,\r\n hint,\r\n error,\r\n htmlFor,\r\n fullWidth = true,\r\n disabled,\r\n group = false,\r\n width,\r\n children,\r\n helpAdornment, // optional <FieldHelp> element\r\n sx,\r\n className,\r\n}: FieldProps) {\r\n // R-I.1 — useId provides SSR-safe, per-instance, stable IDs.\r\n // Replaces the previous module-scoped counter (`let __uid = 0`) which broke\r\n // hydration and Concurrent Mode.\r\n const reactId = React.useId();\r\n const auto = `pui-${reactId.replace(/:/g, \"\")}`;\r\n const inputId = htmlFor || auto;\r\n const hintId = `${inputId}-h`;\r\n const errorId = `${inputId}-e`;\r\n const labelId = `${inputId}-l`;\r\n const hasError = !!error;\r\n\r\n const child = React.Children.only(children);\r\n // The wrapped control is polymorphic (Input/Select/NumberInput/Combobox/…);\r\n // type only the props Field reads/merges, replacing React's `props: any`.\r\n const childProps = child.props as {\r\n id?: string;\r\n disabled?: boolean;\r\n fullWidth?: boolean;\r\n \"aria-describedby\"?: string;\r\n \"aria-labelledby\"?: string;\r\n };\r\n\r\n // R-I.3 — MERGE the child's existing aria-describedby with our hint/error\r\n // ids. cloneElement replaces props, so without an explicit merge any\r\n // consumer-supplied describedby was silently discarded.\r\n const describedBy = [\r\n childProps[\"aria-describedby\"],\r\n hint && !hasError ? hintId : null,\r\n hasError ? errorId : null,\r\n ].filter(Boolean).join(\" \") || undefined;\r\n\r\n // CC-6 — in group mode the label names the group via aria-labelledby.\r\n // Merge with any consumer-supplied labelledby so it is never lost.\r\n const labelledBy = group\r\n ? [childProps[\"aria-labelledby\"], label ? labelId : null]\r\n .filter(Boolean)\r\n .join(\" \") || undefined\r\n : undefined;\r\n\r\n const enhanced = React.cloneElement(child as React.ReactElement<Record<string, unknown>>, {\r\n id: childProps.id || inputId,\r\n \"aria-invalid\": hasError ? \"true\" : undefined,\r\n \"aria-required\": required ? \"true\" : undefined,\r\n \"aria-describedby\": describedBy,\r\n // Only in group mode — a single labelable control is named by the\r\n // htmlFor/id pair below, not by aria-labelledby.\r\n ...(group ? { \"aria-labelledby\": labelledBy } : {}),\r\n // `error` / `disabled` are broadcast by FormControl via context for MUI\r\n // inputs, but several v2 inputs (NumberInput, Combobox renderInput) are\r\n // not pure MUI children and read the props directly — keep them explicit.\r\n error: hasError,\r\n disabled: disabled || childProps.disabled,\r\n // Respect the child's explicit fullWidth when defined (e.g. a narrow\r\n // inline filter input). Only fall back to Field's value when the child\r\n // hasn't expressed an opinion.\r\n fullWidth: childProps.fullWidth !== undefined ? childProps.fullWidth : fullWidth,\r\n });\r\n\r\n // R-I.6 — RTL-safe margin reset. `marginLeft: 0` only zeroes the LTR\r\n // inline-start; in [dir=\"rtl\"] the helper picks up MUI's default on\r\n // the (now physical-right but logical-start) side and is mis-aligned.\r\n // `marginInlineStart` resets the correct edge in both directions.\r\n const helperResetSx = { marginInlineStart: 0, fontSize: \"var(--text-xs)\" };\r\n\r\n const LABEL_SX = {\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n gap: \"var(--s-1)\",\r\n fontSize: \"var(--text-xs, 12px)\",\r\n fontWeight: \"var(--fw-semibold, 600)\",\r\n color: \"var(--fg-muted)\",\r\n marginBottom: \"var(--s-2)\",\r\n \"&.Mui-focused\": { color: \"var(--fg-muted)\" },\r\n \"&.Mui-error\": { color: \"var(--fg-muted)\" },\r\n } as const;\r\n\r\n // Shared label content (text + required marker + optional help adornment).\r\n const labelContent = (\r\n <>\r\n {label}\r\n {required && (\r\n <Box\r\n component=\"span\"\r\n aria-hidden=\"true\"\r\n sx={{ color: \"var(--danger)\", fontWeight: \"var(--fw-bold, 700)\" }}\r\n >\r\n *\r\n </Box>\r\n )}\r\n {helpAdornment}\r\n </>\r\n );\r\n\r\n return (\r\n <FormControl\r\n error={hasError}\r\n required={required}\r\n disabled={disabled}\r\n fullWidth={fullWidth}\r\n className={className}\r\n sx={{\r\n /* G-H — plafond a partir de `sm` seulement : sur mobile le standard\r\n * impose 100% de la largeur de grille. */\r\n ...(width\r\n ? { maxWidth: { xs: \"100%\", sm: FIELD_MAX_WIDTH[width] } }\r\n : {}),\r\n ...sx,\r\n }}\r\n >\r\n {label &&\r\n (group ? (\r\n // Group mode: render as a <span> carrying an id (no <label for>),\r\n // referenced by the group's aria-labelledby.\r\n <FormLabel component=\"span\" id={labelId} required={false} sx={LABEL_SX}>\r\n {labelContent}\r\n </FormLabel>\r\n ) : (\r\n <FormLabel htmlFor={inputId} required={false /* we render the * ourselves */} sx={LABEL_SX}>\r\n {labelContent}\r\n </FormLabel>\r\n ))}\r\n\r\n {enhanced}\r\n\r\n {hint && !hasError && (\r\n <FormHelperText\r\n id={hintId}\r\n sx={{ ...helperResetSx, color: \"var(--fg-subtle)\" }}\r\n >\r\n {hint}\r\n </FormHelperText>\r\n )}\r\n\r\n {hasError && (\r\n /*\r\n * Rendered by the shared `FieldError` (gap G-18) rather than an inline\r\n * `FormHelperText`: R-4.1 MUST requires the icon, and a host with a\r\n * LIST-level error (a Formik array, an upload slot) has no single\r\n * control to wrap in a `Field` and needs the exact same renderer\r\n * standalone. One component, both paths, no drift.\r\n *\r\n * `marginTop: var(--s-1)` (4px) replaces `FormHelperText`'s hard-coded\r\n * 3px default — same optical result, on the spacing scale (R-S.1).\r\n * Dropping FormHelperText also removed a `color: … !important` that\r\n * only existed to beat MUI's own `Mui-error` colour.\r\n */\r\n <FieldError id={errorId} sx={{ marginTop: \"var(--s-1)\" }}>\r\n {error}\r\n </FieldError>\r\n )}\r\n </FormControl>\r\n );\r\n}\r\n\r\nexport default Field;\r\n"],"mappings":";;;;;;AAAA,OAAO,KAAKA,KAAK,MAAM,OAAO;AAC9B,OAAOC,WAAW,MAAM,2BAA2B;AACnD,OAAOC,SAAS,MAAM,yBAAyB;AAC/C,OAAOC,cAAc,MAAM,8BAA8B;AACzD,OAAOC,GAAG,MAAM,mBAAmB;AAEnC,OAAOC,UAAU,MAAM,cAAc;;AAErC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AANA,SAAAC,GAAA,IAAAC,IAAA,EAAAC,QAAA,IAAAC,SAAA,EAAAC,IAAA,IAAAC,KAAA;AASA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,IAAMC,eAA2C,GAAG;EACzDC,EAAE,EAAE,0BAA0B;EAC9BC,CAAC,EAAE,yBAAyB;EAC5BC,CAAC,EAAE,yBAAyB;EAC5BC,CAAC,EAAE,yBAAyB;EAC5BC,EAAE,EAAE;AACN,CAAC;AAgCD,SAASC,KAAKA,CAAAC,IAAA,EAcC;EAAA,IAbbC,KAAK,GAAAD,IAAA,CAALC,KAAK;IAAAC,aAAA,GAAAF,IAAA,CACLG,QAAQ;IAARA,QAAQ,GAAAD,aAAA,cAAG,KAAK,GAAAA,aAAA;IAChBE,IAAI,GAAAJ,IAAA,CAAJI,IAAI;IACJC,KAAK,GAAAL,IAAA,CAALK,KAAK;IACLC,OAAO,GAAAN,IAAA,CAAPM,OAAO;IAAAC,cAAA,GAAAP,IAAA,CACPQ,SAAS;IAATA,SAAS,GAAAD,cAAA,cAAG,IAAI,GAAAA,cAAA;IAChBE,QAAQ,GAAAT,IAAA,CAARS,QAAQ;IAAAC,UAAA,GAAAV,IAAA,CACRW,KAAK;IAALA,KAAK,GAAAD,UAAA,cAAG,KAAK,GAAAA,UAAA;IACbE,KAAK,GAAAZ,IAAA,CAALY,KAAK;IACLC,QAAQ,GAAAb,IAAA,CAARa,QAAQ;IACRC,aAAa,GAAAd,IAAA,CAAbc,aAAa;IACbC,EAAE,GAAAf,IAAA,CAAFe,EAAE;IACFC,SAAS,GAAAhB,IAAA,CAATgB,SAAS;EAET;EACA;EACA;EACA,IAAMC,OAAO,GAAGpC,KAAK,CAACqC,KAAK,CAAC,CAAC;EAC7B,IAAMC,IAAI,UAAAC,MAAA,CAAUH,OAAO,CAACI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAE;EAC/C,IAAMC,OAAO,GAAGhB,OAAO,IAAIa,IAAI;EAC/B,IAAMI,MAAM,MAAAH,MAAA,CAAOE,OAAO,OAAI;EAC9B,IAAME,OAAO,MAAAJ,MAAA,CAAME,OAAO,OAAI;EAC9B,IAAMG,OAAO,MAAAL,MAAA,CAAME,OAAO,OAAI;EAC9B,IAAMI,QAAQ,GAAG,CAAC,CAACrB,KAAK;EAExB,IAAMsB,KAAK,GAAG9C,KAAK,CAAC+C,QAAQ,CAACC,IAAI,CAAChB,QAAQ,CAAC;EAC3C;EACA;EACA,IAAMiB,UAAU,GAAGH,KAAK,CAACI,KAMxB;;EAED;EACA;EACA;EACA,IAAMC,WAAW,GAAG,CAClBF,UAAU,CAAC,kBAAkB,CAAC,EAC9B1B,IAAI,IAAI,CAACsB,QAAQ,GAAGH,MAAM,GAAG,IAAI,EACjCG,QAAQ,GAAGF,OAAO,GAAG,IAAI,CAC1B,CAACS,MAAM,CAACC,OAAO,CAAC,CAACC,IAAI,CAAC,GAAG,CAAC,IAAIC,SAAS;;EAExC;EACA;EACA,IAAMC,UAAU,GAAG1B,KAAK,GACpB,CAACmB,UAAU,CAAC,iBAAiB,CAAC,EAAE7B,KAAK,GAAGwB,OAAO,GAAG,IAAI,CAAC,CACpDQ,MAAM,CAACC,OAAO,CAAC,CACfC,IAAI,CAAC,GAAG,CAAC,IAAIC,SAAS,GACzBA,SAAS;EAEb,IAAME,QAAQ,gBAAGzD,KAAK,CAAC0D,YAAY,CAACZ,KAAK,EAAAa,aAAA,CAAAA,aAAA;IACvCC,EAAE,EAAEX,UAAU,CAACW,EAAE,IAAInB,OAAO;IAC5B,cAAc,EAAII,QAAQ,GAAG,MAAM,GAAGU,SAAS;IAC/C,eAAe,EAAGjC,QAAQ,GAAG,MAAM,GAAGiC,SAAS;IAC/C,kBAAkB,EAAEJ;EAAW,GAG3BrB,KAAK,GAAG;IAAE,iBAAiB,EAAE0B;EAAW,CAAC,GAAG,CAAC,CAAC;IAClD;IACA;IACA;IACAhC,KAAK,EAAEqB,QAAQ;IACfjB,QAAQ,EAAEA,QAAQ,IAAIqB,UAAU,CAACrB,QAAQ;IACzC;IACA;IACA;IACAD,SAAS,EAAEsB,UAAU,CAACtB,SAAS,KAAK4B,SAAS,GAAGN,UAAU,CAACtB,SAAS,GAAGA;EAAS,EACjF,CAAC;;EAEF;EACA;EACA;EACA;EACA,IAAMkC,aAAa,GAAG;IAAEC,iBAAiB,EAAE,CAAC;IAAEC,QAAQ,EAAE;EAAiB,CAAC;EAE1E,IAAMC,QAAQ,GAAG;IACfC,OAAO,EAAE,MAAM;IACfC,UAAU,EAAE,QAAQ;IACpBC,GAAG,EAAE,YAAY;IACjBJ,QAAQ,EAAE,sBAAsB;IAChCK,UAAU,EAAE,yBAAyB;IACrCC,KAAK,EAAE,iBAAiB;IACxBC,YAAY,EAAE,YAAY;IAC1B,eAAe,EAAE;MAAED,KAAK,EAAE;IAAkB,CAAC;IAC7C,aAAa,EAAI;MAAEA,KAAK,EAAE;IAAkB;EAC9C,CAAU;;EAEV;EACA,IAAME,YAAY,gBAChB5D,KAAA,CAAAF,SAAA;IAAAuB,QAAA,GACGZ,KAAK,EACLE,QAAQ,iBACPf,IAAA,CAACH,GAAG;MACFoE,SAAS,EAAC,MAAM;MAChB,eAAY,MAAM;MAClBtC,EAAE,EAAE;QAAEmC,KAAK,EAAE,eAAe;QAAED,UAAU,EAAE;MAAsB,CAAE;MAAApC,QAAA,EACnE;IAED,CAAK,CACN,EACAC,aAAa;EAAA,CACd,CACH;EAED,oBACEtB,KAAA,CAACV,WAAW;IACVuB,KAAK,EAAEqB,QAAS;IAChBvB,QAAQ,EAAEA,QAAS;IACnBM,QAAQ,EAAEA,QAAS;IACnBD,SAAS,EAAEA,SAAU;IACrBQ,SAAS,EAAEA,SAAU;IACrBD,EAAE,EAAAyB,aAAA,CAAAA,aAAA,KAGI5B,KAAK,GACL;MAAE0C,QAAQ,EAAE;QAAE5D,EAAE,EAAE,MAAM;QAAE6D,EAAE,EAAE9D,eAAe,CAACmB,KAAK;MAAE;IAAE,CAAC,GACxD,CAAC,CAAC,GACHG,EAAE,CACL;IAAAF,QAAA,GAEDZ,KAAK,KACHU,KAAK;IAAA;IACJ;IACA;IACAvB,IAAA,CAACL,SAAS;MAACsE,SAAS,EAAC,MAAM;MAACZ,EAAE,EAAEhB,OAAQ;MAACtB,QAAQ,EAAE,KAAM;MAACY,EAAE,EAAE8B,QAAS;MAAAhC,QAAA,EACpEuC;IAAY,CACJ,CAAC,gBAEZhE,IAAA,CAACL,SAAS;MAACuB,OAAO,EAAEgB,OAAQ;MAACnB,QAAQ,EAAE,KAAK,CAAC,+BAAgC;MAACY,EAAE,EAAE8B,QAAS;MAAAhC,QAAA,EACxFuC;IAAY,CACJ,CACZ,CAAC,EAEHd,QAAQ,EAERlC,IAAI,IAAI,CAACsB,QAAQ,iBAChBtC,IAAA,CAACJ,cAAc;MACbyD,EAAE,EAAElB,MAAO;MACXR,EAAE,EAAAyB,aAAA,CAAAA,aAAA,KAAOE,aAAa;QAAEQ,KAAK,EAAE;MAAkB,EAAG;MAAArC,QAAA,EAEnDT;IAAI,CACS,CACjB,EAEAsB,QAAQ;IAAA;IACP;AACR;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACQtC,IAAA,CAACF,UAAU;MAACuD,EAAE,EAAEjB,OAAQ;MAACT,EAAE,EAAE;QAAEyC,SAAS,EAAE;MAAa,CAAE;MAAA3C,QAAA,EACtDR;IAAK,CACI,CACb;EAAA,CACU,CAAC;AAElB;AAEA,eAAeN,KAAK","ignoreList":[]}
|
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
export { default as Spinner } from "./Spinner";
|
|
2
2
|
export { default as Button } from "./Button";
|
|
3
3
|
export { default as Field } from "./Field";
|
|
4
|
+
/**
|
|
5
|
+
* Field contracts — public typed surface (gap G-H).
|
|
6
|
+
*
|
|
7
|
+
* Meme defaut que les blocs `StepStatus`, `FieldHelp` et `Status` plus bas :
|
|
8
|
+
* `Field` etait exporte comme VALEUR, sans aucun de ses types. `FieldWidth` est
|
|
9
|
+
* le plus necessaire des deux : la section « Form Field Width & Layout
|
|
10
|
+
* Standards » des Platform Mandatory UX Patterns impose de rattacher CHAQUE
|
|
11
|
+
* champ d'un formulaire a l'un des cinq gabarits, et un module qui fait ce
|
|
12
|
+
* mappage a l'echelle d'un ecran ecrit naturellement un dictionnaire :
|
|
13
|
+
*
|
|
14
|
+
* const LARGEURS: Record<string, FieldWidth> = { ifu: "s", adresse: "l", ... }
|
|
15
|
+
*
|
|
16
|
+
* Sans le type, il doit reecrire l'union au point d'appel — ce que les Platform
|
|
17
|
+
* TypeScript Guidelines interdisent (« Reusable types belong inside Shared UI
|
|
18
|
+
* Library … Never duplicate »). C'est le cas signale depuis `refonte-frontend`
|
|
19
|
+
* (lot 8), ou ~60 champs sont a mapper.
|
|
20
|
+
*
|
|
21
|
+
* `FIELD_MAX_WIDTH` n'est PAS surfacee, deliberement : les cinq largeurs sont
|
|
22
|
+
* une decision de design, et les exposer inviterait les hotes a poser les
|
|
23
|
+
* plafonds eux-memes plutot que par `width` — donc a recreer les « arbitrary
|
|
24
|
+
* input field widths » que le standard interdit. Elle reste atteignable par
|
|
25
|
+
* chemin interne pour le test qui les epingle.
|
|
26
|
+
*
|
|
27
|
+
* FLAT-ONLY, per the policy note in src/lib/index.ts.
|
|
28
|
+
* Additive and type-only: no runtime change, no existing export altered.
|
|
29
|
+
*/
|
|
30
|
+
|
|
4
31
|
/**
|
|
5
32
|
* FieldError — the message renderer `Field` uses, exported standalone
|
|
6
33
|
* (capability gap G-18).
|