brut 0.9.1 → 0.10.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/Gemfile.lock +1 -1
- data/bin/new-version +8 -0
- data/brut-css/package-lock.json +2 -2
- data/brut-css/package.json +1 -1
- data/brut-js/package-lock.json +2 -2
- data/brut-js/package.json +1 -1
- data/brut-js/specs/ConstraintViolationMessages.spec.js +4 -1
- data/brut-js/specs/Form.spec.js +2 -46
- data/brut-js/src/ConstraintViolationMessage.js +17 -2
- data/brut-js/src/Form.js +19 -23
- data/brutrb.com/.vitepress/config.mjs +1 -0
- data/brutrb.com/form-constraints.md +6 -6
- data/brutrb.com/recipes/form-errors.md +148 -0
- data/docs/404.html +2 -2
- data/docs/adrs.html +3 -3
- data/docs/ai.html +3 -3
- data/docs/api/Brut/BackEnd/SeedData.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
- data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
- data/docs/api/Brut/BackEnd/Validators.html +1 -1
- data/docs/api/Brut/BackEnd.html +1 -1
- data/docs/api/Brut/CLI/App.html +1 -1
- data/docs/api/Brut/CLI/AppRunner.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps.html +1 -1
- data/docs/api/Brut/CLI/Command.html +1 -1
- data/docs/api/Brut/CLI/Error.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
- data/docs/api/Brut/CLI/Executor.html +1 -1
- data/docs/api/Brut/CLI/InvalidOption.html +1 -1
- data/docs/api/Brut/CLI/Options.html +1 -1
- data/docs/api/Brut/CLI/Output.html +1 -1
- data/docs/api/Brut/CLI/SystemExecError.html +1 -1
- data/docs/api/Brut/CLI.html +1 -1
- data/docs/api/Brut/FactoryBot.html +1 -1
- data/docs/api/Brut/Framework/App.html +1 -1
- data/docs/api/Brut/Framework/Config.html +1 -1
- data/docs/api/Brut/Framework/Container.html +1 -1
- data/docs/api/Brut/Framework/Error.html +1 -1
- data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
- data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
- data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
- data/docs/api/Brut/Framework/Errors.html +1 -1
- data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
- data/docs/api/Brut/Framework/MCP.html +1 -1
- data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
- data/docs/api/Brut/Framework.html +1 -1
- data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
- data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
- data/docs/api/Brut/FrontEnd/Component.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
- data/docs/api/Brut/FrontEnd/Components.html +1 -1
- data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
- data/docs/api/Brut/FrontEnd/Download.html +1 -1
- data/docs/api/Brut/FrontEnd/Flash.html +1 -1
- data/docs/api/Brut/FrontEnd/Form.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms.html +1 -1
- data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
- data/docs/api/Brut/FrontEnd/Handler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
- data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
- data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
- data/docs/api/Brut/FrontEnd/Layout.html +1 -1
- data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
- data/docs/api/Brut/FrontEnd/Page.html +1 -1
- data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
- data/docs/api/Brut/FrontEnd/Pages.html +1 -1
- data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing.html +1 -1
- data/docs/api/Brut/FrontEnd/Session.html +1 -1
- data/docs/api/Brut/FrontEnd.html +1 -1
- data/docs/api/Brut/I18n/BaseMethods.html +1 -1
- data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
- data/docs/api/Brut/I18n/ForCLI.html +1 -1
- data/docs/api/Brut/I18n/ForHTML.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
- data/docs/api/Brut/I18n.html +1 -1
- data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
- data/docs/api/Brut/Instrumentation.html +1 -1
- data/docs/api/Brut/RubocopConfig.html +1 -1
- data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
- data/docs/api/Brut/SinatraHelpers.html +1 -1
- data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
- data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
- data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
- data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
- data/docs/api/Brut/SpecSupport.html +1 -1
- data/docs/api/Brut.html +1 -1
- data/docs/api/Clock.html +1 -1
- data/docs/api/ModuleName.html +1 -1
- data/docs/api/RichString.html +1 -1
- data/docs/api/SemanticLogger/Appender/Async.html +1 -1
- data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
- data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
- data/docs/api/Sequel/Extensions.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang.html +1 -1
- data/docs/api/Sequel/Plugins.html +1 -1
- data/docs/api/Sequel.html +1 -1
- data/docs/api/_index.html +1 -1
- data/docs/api/file.README.html +1 -1
- data/docs/api/index.html +1 -1
- data/docs/api/top-level-namespace.html +1 -1
- data/docs/assets/{app.BuBdZoUg.js → app.vjGWMSnJ.js} +1 -1
- data/docs/assets/chunks/@localSearchIndexroot.Dn1xGMv_.js +1 -0
- data/docs/assets/chunks/{VPLocalSearchBox.UtnyvkX-.js → VPLocalSearchBox.C-ymMW2k.js} +1 -1
- data/docs/assets/chunks/{theme.DqwvuBEK.js → theme.pJUosGlI.js} +2 -2
- data/docs/assets/{components.md.Bu80E2Nr.js → components.md.B543a3Lm.js} +3 -3
- data/docs/assets/{configuration.md.CuIxVsSf.js → configuration.md.-foE_iVv.js} +1 -1
- data/docs/assets/{forms.md.DnLbzVDa.js → forms.md.D5-2rgHh.js} +1 -1
- data/docs/assets/{getting-started.md.DdQLmU3C.js → getting-started.md.Cd4XSZb_.js} +3 -3
- data/docs/assets/{getting-started.md.DdQLmU3C.lean.js → getting-started.md.Cd4XSZb_.lean.js} +1 -1
- data/docs/assets.html +3 -3
- data/docs/brut-js/api/AjaxSubmit.html +1 -1
- data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
- data/docs/brut-js/api/Autosubmit.html +1 -1
- data/docs/brut-js/api/Autosubmit.js.html +1 -1
- data/docs/brut-js/api/BaseCustomElement.html +1 -1
- data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
- data/docs/brut-js/api/BrutCustomElements.html +1 -1
- data/docs/brut-js/api/BufferedLogger.html +1 -1
- data/docs/brut-js/api/ConfirmSubmit.html +1 -1
- data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
- data/docs/brut-js/api/ConfirmationDialog.html +1 -1
- data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
- data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
- data/docs/brut-js/api/CopyToClipboard.html +1 -1
- data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
- data/docs/brut-js/api/Form.html +1 -1
- data/docs/brut-js/api/Form.js.html +1 -1
- data/docs/brut-js/api/I18nTranslation.html +1 -1
- data/docs/brut-js/api/I18nTranslation.js.html +1 -1
- data/docs/brut-js/api/LocaleDetection.html +1 -1
- data/docs/brut-js/api/LocaleDetection.js.html +1 -1
- data/docs/brut-js/api/Logger.html +1 -1
- data/docs/brut-js/api/Logger.js.html +1 -1
- data/docs/brut-js/api/Message.html +1 -1
- data/docs/brut-js/api/Message.js.html +1 -1
- data/docs/brut-js/api/PrefixedLogger.html +1 -1
- data/docs/brut-js/api/RichString.html +1 -1
- data/docs/brut-js/api/RichString.js.html +1 -1
- data/docs/brut-js/api/Tabs.html +1 -1
- data/docs/brut-js/api/Tabs.js.html +1 -1
- data/docs/brut-js/api/Tracing.html +1 -1
- data/docs/brut-js/api/Tracing.js.html +1 -1
- data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
- data/docs/brut-js/api/external-Performance.html +1 -1
- data/docs/brut-js/api/external-Promise.html +1 -1
- data/docs/brut-js/api/external-ValidityState.html +1 -1
- data/docs/brut-js/api/external-Window.html +1 -1
- data/docs/brut-js/api/external-fetch.html +1 -1
- data/docs/brut-js/api/global.html +1 -1
- data/docs/brut-js/api/index.html +1 -1
- data/docs/brut-js/api/index.js.html +1 -1
- data/docs/brut-js/api/module-testing.html +1 -1
- data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
- data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
- data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
- data/docs/brut-js/api/testing.DOMCreator.html +1 -1
- data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
- data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
- data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
- data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
- data/docs/brut-js/api/testing_index.js.html +1 -1
- data/docs/brut-js.html +3 -3
- data/docs/business-logic.html +3 -3
- data/docs/cli.html +3 -3
- data/docs/components.html +7 -7
- data/docs/configuration.html +5 -5
- data/docs/css.html +3 -3
- data/docs/custom-element-tests.html +3 -3
- data/docs/database-access.html +3 -3
- data/docs/database-schema.html +3 -3
- data/docs/deployment.html +3 -3
- data/docs/dev-environment.html +3 -3
- data/docs/dir-structure.html +3 -3
- data/docs/doc-conventions.html +3 -3
- data/docs/end-to-end-tests.html +3 -3
- data/docs/features.html +3 -3
- data/docs/flash-and-session.html +3 -3
- data/docs/form-constraints.html +3 -3
- data/docs/forms.html +5 -5
- data/docs/getting-started.html +6 -6
- data/docs/handlers.html +3 -3
- data/docs/hashmap.json +1 -1
- data/docs/hooks.html +3 -3
- data/docs/i18n.html +3 -3
- data/docs/index.html +3 -3
- data/docs/instrumentation.html +3 -3
- data/docs/javascript.html +3 -3
- data/docs/jobs.html +3 -3
- data/docs/keyword-injection.html +3 -3
- data/docs/layouts.html +3 -3
- data/docs/lsp.html +3 -3
- data/docs/markdown-examples.html +3 -3
- data/docs/middleware.html +3 -3
- data/docs/overview.html +3 -3
- data/docs/pages.html +3 -3
- data/docs/recipes/alternate-layouts.html +3 -3
- data/docs/recipes/authentication.html +3 -3
- data/docs/recipes/blank-layouts.html +3 -3
- data/docs/recipes/custom-flash.html +3 -3
- data/docs/recipes/indexed-forms.html +3 -3
- data/docs/recipes/migrations.html +3 -3
- data/docs/recipes/text-field-component.html +3 -3
- data/docs/roadmap.html +3 -3
- data/docs/routes.html +3 -3
- data/docs/security.html +3 -3
- data/docs/seed-data.html +3 -3
- data/docs/space-time-continuum.html +3 -3
- data/docs/tutorial.html +3 -3
- data/docs/unit-tests.html +3 -3
- data/docs/why.html +3 -3
- data/lib/brut/front_end/components/constraint_violations.rb +23 -10
- data/lib/brut/version.rb +1 -1
- data/mkbrut/Gemfile.lock +1 -1
- data/mkbrut/lib/mkbrut/version.rb +1 -1
- data/mkbrut/templates/Base/Dockerfile.dx +2 -2
- data/mkbrut/templates/segments/Heroku/deploy/Dockerfile +4 -5
- metadata +15 -14
- data/.nvim.lua +0 -1
- data/docs/assets/chunks/@localSearchIndexroot.BZ_a3X0T.js +0 -1
- /data/docs/assets/{components.md.Bu80E2Nr.lean.js → components.md.B543a3Lm.lean.js} +0 -0
- /data/docs/assets/{configuration.md.CuIxVsSf.lean.js → configuration.md.-foE_iVv.lean.js} +0 -0
- /data/docs/assets/{forms.md.DnLbzVDa.lean.js → forms.md.D5-2rgHh.lean.js} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bb034356c94bcb184b5ded4c4d6d07d46d85e28371cbe7fd17ea999c78e1932e
|
4
|
+
data.tar.gz: 90339e125f3b8b9bde9bda634f4934d0c0a09955141cda41969c9347b7b86877
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ad76fb17f0247373ded5eb4df807eab9f32db39633082afb647b6b268d6d74f1529b433f3a3a06c5cd101e79d841f3533bef589940de3d629af3c7c400096d0
|
7
|
+
data.tar.gz: 50acf68486d2bbcb6600fb2cf0e21f62ec879ba1a3f0767557d29f9e2260229cc4bdad4b869d800cfdc7101723eca7afb4498577f44b22095a24526ec9526a45
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Brut CHANGELOG
|
2
2
|
|
3
|
+
## v0.10.0 - Aug 15, 2025
|
4
|
+
|
5
|
+
* Changed `ConstraintViolations` HTML to indicate if a `<brut-cv>` is server-generated, even if it's generating client-side constraint. See #56.
|
6
|
+
* `<brut-form>` now requires that all `<brut-cv-messages>` have an `input-name`.
|
7
|
+
|
8
|
+
|
9
|
+
## v0.9.2 - Aug 14, 2025
|
10
|
+
|
11
|
+
* Updated Docker instructions for installing PostgreSQL client, due to ruby:3.4 image upgrading to Debian Trixie which removed something. See https://github.com/thirdtank/brut/issues/55
|
12
|
+
|
3
13
|
## v0.9.1 - Aug 12, 2025
|
4
14
|
|
5
15
|
* Changed `<brut-confirm-submit>` to not ask for confirmation if the form is invalid
|
data/Gemfile.lock
CHANGED
data/bin/new-version
ADDED
data/brut-css/package-lock.json
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
{
|
2
2
|
"name": "brut-css",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.10.0",
|
4
4
|
"lockfileVersion": 3,
|
5
5
|
"requires": true,
|
6
6
|
"packages": {
|
7
7
|
"": {
|
8
8
|
"name": "brut-css",
|
9
|
-
"version": "0.
|
9
|
+
"version": "0.10.0",
|
10
10
|
"license": "Hippocratic-2.1",
|
11
11
|
"devDependencies": {
|
12
12
|
"comment-parser": "^1.4.1",
|
data/brut-css/package.json
CHANGED
data/brut-js/package-lock.json
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
{
|
2
2
|
"name": "brut-js",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.10.0",
|
4
4
|
"lockfileVersion": 3,
|
5
5
|
"requires": true,
|
6
6
|
"packages": {
|
7
7
|
"": {
|
8
8
|
"name": "brut-js",
|
9
|
-
"version": "0.
|
9
|
+
"version": "0.10.0",
|
10
10
|
"license": "Hippocratic-2.1",
|
11
11
|
"devDependencies": {
|
12
12
|
"esbuild": "^0.24.0",
|
data/brut-js/package.json
CHANGED
@@ -18,9 +18,12 @@ describe("<brut-cv-messages>", () => {
|
|
18
18
|
|
19
19
|
element.createMessages({validityState,inputName})
|
20
20
|
|
21
|
-
|
21
|
+
const brutCvElements = element.querySelectorAll("brut-cv")
|
22
|
+
assert.equal(brutCvElements.length, 2)
|
22
23
|
assert.match(element.textContent,new RegExp("This field does not match the pattern","m"))
|
23
24
|
assert.match(element.textContent,new RegExp("This field is above the range","m"))
|
25
|
+
assert.equal(brutCvElements[0].getAttribute("client-side"),"")
|
26
|
+
assert.equal(brutCvElements[1].getAttribute("client-side"),"")
|
24
27
|
|
25
28
|
element.clearClientSideMessages()
|
26
29
|
assert.equal(element.textContent,"")
|
data/brut-js/specs/Form.spec.js
CHANGED
@@ -6,12 +6,12 @@ describe("<brut-form>", () => {
|
|
6
6
|
<form>
|
7
7
|
<label>
|
8
8
|
<input required type="text" name="text">
|
9
|
-
<brut-cv-messages>
|
9
|
+
<brut-cv-messages input-name="text">
|
10
10
|
</brut-cv-messages>
|
11
11
|
</label>
|
12
12
|
<label>
|
13
13
|
<input required type="number" name="number">
|
14
|
-
<brut-cv-messages>
|
14
|
+
<brut-cv-messages input-name="number">
|
15
15
|
</brut-cv-messages>
|
16
16
|
</label>
|
17
17
|
<input type="submit">Save</input>
|
@@ -134,48 +134,4 @@ describe("<brut-form>", () => {
|
|
134
134
|
assert(gotValid)
|
135
135
|
assert(!gotInvalid)
|
136
136
|
})
|
137
|
-
withHTML(`
|
138
|
-
<brut-form>
|
139
|
-
<form>
|
140
|
-
<label>
|
141
|
-
<input required type="text" name="text">
|
142
|
-
</label>
|
143
|
-
<brut-cv-messages input-name='text'>
|
144
|
-
</brut-cv-messages>
|
145
|
-
<input type="submit">Save</input>
|
146
|
-
</form>
|
147
|
-
</brut-form>
|
148
|
-
`).test("locates the messages for errors based on name", ({window,document,assert}) => {
|
149
|
-
|
150
|
-
const brutForm = document.querySelector("brut-form")
|
151
|
-
const form = brutForm.querySelector("form")
|
152
|
-
const button = form.querySelector("input[type=submit]")
|
153
|
-
const textFieldLabel = form.querySelector("label:has(input[type=text])")
|
154
|
-
|
155
|
-
let submitted = false
|
156
|
-
let gotInvalid = false
|
157
|
-
let gotValid = false
|
158
|
-
|
159
|
-
form.addEventListener("submit", (event) => {
|
160
|
-
event.preventDefault()
|
161
|
-
submitted = true
|
162
|
-
})
|
163
|
-
brutForm.addEventListener("brut:valid", () => {
|
164
|
-
gotValid = true
|
165
|
-
})
|
166
|
-
brutForm.addEventListener("brut:invalid", () => {
|
167
|
-
gotInvalid = true
|
168
|
-
})
|
169
|
-
|
170
|
-
button.click()
|
171
|
-
|
172
|
-
assert(!submitted)
|
173
|
-
assert(!gotValid)
|
174
|
-
assert(gotInvalid)
|
175
|
-
assert.equal(brutForm.getAttribute("submitted-invalid"),"")
|
176
|
-
|
177
|
-
let error = brutForm.querySelector("brut-cv[input-name='text'][key='cv.cs.valueMissing']")
|
178
|
-
assert(error)
|
179
|
-
|
180
|
-
})
|
181
137
|
})
|
@@ -15,9 +15,15 @@ import I18nTranslation from "./I18nTranslation"
|
|
15
15
|
* @property {string} key - the i18n translation key to use. It must map to the `key` of a `<brut-i18n-translation>` on the page or
|
16
16
|
* the element will not render any text.
|
17
17
|
* @property {string} input-name - the name of the input, used to insert into the message, e.g. "Title is required".
|
18
|
+
* @property {boolean} server-generated if true, this indicates the element's HTML was generated on the server.
|
19
|
+
* This means that your CSS can target it for display in all cases. If this is not present,
|
20
|
+
* you may want to avoid showing this element if the form has not been submitted yet.
|
21
|
+
* Does not affect behavior.
|
18
22
|
* @property {boolean} server-side if true, this indicates the element contains constraint violation messages
|
19
|
-
*
|
20
|
-
*
|
23
|
+
* from the server. Does not affect behavior.
|
24
|
+
* @property {boolean} client-side if true, this indicates the element contains constraint violation messages
|
25
|
+
* from the client, however they may have been generated from the server, since the server may
|
26
|
+
* re-evaluate the client-side constraints. Does not affect behavior of this tag.
|
21
27
|
*
|
22
28
|
* @see I18nTranslation
|
23
29
|
* @see ConstraintViolationMessages
|
@@ -33,12 +39,15 @@ class ConstraintViolationMessage extends BaseCustomElement {
|
|
33
39
|
"key",
|
34
40
|
"input-name",
|
35
41
|
"server-side",
|
42
|
+
"client-side",
|
43
|
+
"server-generated",
|
36
44
|
]
|
37
45
|
|
38
46
|
static createElement(document,attributes) {
|
39
47
|
const element = document.createElement(ConstraintViolationMessage.tagName)
|
40
48
|
element.setAttribute("key",this.i18nKey("cs", attributes.key))
|
41
49
|
element.setAttribute("input-name",attributes["input-name"])
|
50
|
+
element.setAttribute("client-side","")
|
42
51
|
if (Object.hasOwn(attributes,"show-warnings")) {
|
43
52
|
element.setAttribute("show-warnings",attributes["show-warnings"])
|
44
53
|
}
|
@@ -85,6 +94,12 @@ class ConstraintViolationMessage extends BaseCustomElement {
|
|
85
94
|
serverSideChangedCallback({newValueAsBoolean}) {
|
86
95
|
// attribute listed for documentation purposes only
|
87
96
|
}
|
97
|
+
clientSideChangedCallback({newValueAsBoolean}) {
|
98
|
+
// attribute listed for documentation purposes only
|
99
|
+
}
|
100
|
+
serverGeneratedChangedCallback({newValueAsBoolean}) {
|
101
|
+
// attribute listed for documentation purposes only
|
102
|
+
}
|
88
103
|
|
89
104
|
update() {
|
90
105
|
if (!this.#key) {
|
data/brut-js/src/Form.js
CHANGED
@@ -15,10 +15,7 @@ import ConstraintViolationMessages from "./ConstraintViolationMessages"
|
|
15
15
|
* set `submitted-invalid` on itself when that happens, thus allowing you to target invalid
|
16
16
|
* fields only after a submission attempt.
|
17
17
|
* * You may wish to control the messaging of client-side constraint violations
|
18
|
-
* beyond what the browser gives you. Assuming
|
19
|
-
* like `LABEL`, a `brut-cv` tag found in that container
|
20
|
-
* (i.e. a sibling of your `INPUT`) will be modified to contain error messages specific
|
21
|
-
* to the {@link external:ValidityState} of the control.
|
18
|
+
* beyond what the browser gives you. Assuming you have generated a `<brut-cv-messages input-name="«input name»"></brut-cv-messasges>`, it will be populated with `<brut-cv>` elements for each client-side constraint violation, based on the {@link external:ValidityState} of the control.
|
22
19
|
*
|
23
20
|
* @fires brut:invalid Fired when any element is found to be invalid
|
24
21
|
* @fires brut:valid Fired when no element is found to be invalid. This should be reliable to know
|
@@ -29,12 +26,12 @@ import ConstraintViolationMessages from "./ConstraintViolationMessages"
|
|
29
26
|
* <form ...>
|
30
27
|
* <label>
|
31
28
|
* <input type="text" required name="username">
|
32
|
-
* <brut-cv-messages>
|
29
|
+
* <brut-cv-messages input-name="username">
|
33
30
|
* </brut-cv-messages>
|
34
31
|
* </label>
|
35
32
|
* <div> <!-- container need not be a label -->
|
36
33
|
* <input type="text" required minlength="4" name="alias">
|
37
|
-
* <brut-cv-messages>
|
34
|
+
* <brut-cv-messages input-name="alias">
|
38
35
|
* </brut-cv-messages>
|
39
36
|
* </div>
|
40
37
|
* <button>Submit</button>
|
@@ -45,13 +42,13 @@ import ConstraintViolationMessages from "./ConstraintViolationMessages"
|
|
45
42
|
* <form ...>
|
46
43
|
* <label>
|
47
44
|
* <input type="text" required name="username">
|
48
|
-
* <brut-cv-messages>
|
45
|
+
* <brut-cv-messages input-name="username">
|
49
46
|
* <brut-cv>This field is required</brut-cv>
|
50
47
|
* </brut-cv-messages>
|
51
48
|
* </label>
|
52
49
|
* <div> <!-- container need not be a label -->
|
53
50
|
* <input type="text" required minlength="4" name="alias">
|
54
|
-
* <brut-cv-messages>
|
51
|
+
* <brut-cv-messages input-name="alias">
|
55
52
|
* <brut-cv>This field is required</brut-cv>
|
56
53
|
* </brut-cv-messages>
|
57
54
|
* </div>
|
@@ -107,28 +104,27 @@ class Form extends BaseCustomElement {
|
|
107
104
|
|
108
105
|
#updateErrorMessages(event) {
|
109
106
|
const element = event.target
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
107
|
+
let constraintViolationMessages = []
|
108
|
+
if (element.name && element.form) {
|
109
|
+
const selector = `${ConstraintViolationMessages.tagName}[input-name='${element.name}']`
|
110
|
+
constraintViolationMessages = element.form.querySelectorAll(selector)
|
111
|
+
if (constraintViolationMessages.length == 0) {
|
112
|
+
this.logger.warn(`Did not find any elements matching ${selector}, so no error messages will be shown`)
|
113
|
+
}
|
114
|
+
}
|
115
|
+
else {
|
116
|
+
if (element.name) {
|
117
|
+
this.logger.warn("Element has a name (%s), but is not associated with any form.", element.name)
|
119
118
|
}
|
120
119
|
else {
|
121
|
-
this.logger.warn("
|
122
|
-
selector,
|
123
|
-
element.name ? "no name" : "a name, but",
|
124
|
-
element.form ? "no form" : "though has a form")
|
120
|
+
this.logger.warn("Element has a form, but has no name, which means we cannot locate %s by input-name", ConstraintViolationMessages.tagName)
|
125
121
|
}
|
126
122
|
}
|
127
|
-
if (
|
123
|
+
if (constraintViolationMessages.length == 0) {
|
128
124
|
return
|
129
125
|
}
|
130
126
|
let anyErrors = false
|
131
|
-
|
127
|
+
constraintViolationMessages.forEach( (errorLabel) => {
|
132
128
|
if (element.validity.valid) {
|
133
129
|
errorLabel.clearClientSideMessages()
|
134
130
|
}
|
@@ -130,6 +130,7 @@ export default defineConfig({
|
|
130
130
|
collapsed: true,
|
131
131
|
items: [
|
132
132
|
{ text: "Migration Basics", link: "/recipes/migrations" },
|
133
|
+
{ text: "Styling Form Errors", link: "/recipes/form-errors" },
|
133
134
|
{ text: "Authentication", link: "/recipes/authentication" },
|
134
135
|
{ text: "Alternate Layouts", link: "/recipes/alternate-layouts" },
|
135
136
|
{ text: "Blank Layouts", link: "/recipes/blank-layouts" },
|
@@ -120,7 +120,7 @@ inputs `ValidityState`. That may look like so:
|
|
120
120
|
```html {3}
|
121
121
|
<input type="text" name="name" required minlength="3">
|
122
122
|
<brut-cv-messages input-name="name">
|
123
|
-
<brut-cv input-name="name" key="rangeUnderflow"></brut-cv>
|
123
|
+
<brut-cv input-name="name" client-side key="rangeUnderflow"></brut-cv>
|
124
124
|
</brut-cv-messages>
|
125
125
|
```
|
126
126
|
|
@@ -139,7 +139,7 @@ this HTML:
|
|
139
139
|
```html {4}
|
140
140
|
<input type="text" name="name" required minlength="3">
|
141
141
|
<brut-cv-messages input-name="name">
|
142
|
-
<brut-cv input-name="name" key="rangeUnderflow">
|
142
|
+
<brut-cv input-name="name" client-side key="rangeUnderflow">
|
143
143
|
This field is too short
|
144
144
|
</brut-cv>
|
145
145
|
</brut-cv-messages>
|
@@ -154,13 +154,13 @@ violation, the same general markup is generated:
|
|
154
154
|
```html {3,4}
|
155
155
|
<input type="text" name="name" required minlength="3">
|
156
156
|
<brut-cv-messages input-name="name">
|
157
|
-
<brut-cv server-side>
|
157
|
+
<brut-cv server-generated server-side>
|
158
158
|
This name has already been taken.
|
159
159
|
</brut-cv>
|
160
160
|
</brut-cv-messages>
|
161
161
|
```
|
162
162
|
|
163
|
-
The `server-side`
|
163
|
+
The `server-genereted` and `server-side` attributes is set, which can help with CSS targeting.
|
164
164
|
|
165
165
|
The *last* piece of this puzzle is a solution for the issue where forms that have
|
166
166
|
not yet been submitted are considered to have invalid values by the browser.
|
@@ -177,7 +177,7 @@ This might lead to HTML like so:
|
|
177
177
|
|
178
178
|
<input type="text" name="name" required minlength="3">
|
179
179
|
<brut-cv-messages input-name="name">
|
180
|
-
<brut-cv input-name="name" key="rangeUnderflow">
|
180
|
+
<brut-cv input-name="name" client-side key="rangeUnderflow">
|
181
181
|
This field is too short
|
182
182
|
</brut-cv>
|
183
183
|
</brut-cv-messages>
|
@@ -199,7 +199,7 @@ brut-cv {
|
|
199
199
|
/* brut-cv inside a submitted-invalid
|
200
200
|
OR brut-cv from the server ARE shown */
|
201
201
|
brut-form[submitted-invalid] brut-cv,
|
202
|
-
brut-cv[server-
|
202
|
+
brut-cv[server-generated] {
|
203
203
|
display: block;
|
204
204
|
color: red; /* e.g. */
|
205
205
|
}
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# Styling Form Errors
|
2
|
+
|
3
|
+
Brut makes it as easy as possible to unify client-side and server-side constraint violation handling, including
|
4
|
+
how you style those messages.
|
5
|
+
|
6
|
+
## Requirements
|
7
|
+
|
8
|
+
What you want:
|
9
|
+
|
10
|
+
* When a form is rendered for the first time, there should be errors shown.
|
11
|
+
* When a visitor interacts with a form before submissions, no errors are shown.
|
12
|
+
* When the form is submitted, client-side constraint violations should be shown.
|
13
|
+
* When JavaScript is circumvented and the form is submitted with client-side constraint violations, the form should be re-generated, showing those violations the same is if JavaScripts was *not* circumvented
|
14
|
+
* When there are no client-side constraint violations, but there *are* server-side violations, the form should be re-generated, showing those violations the same is if JavaScripts was *not* circumvented
|
15
|
+
|
16
|
+
This can be achieved through CSS.
|
17
|
+
|
18
|
+
## Recipe
|
19
|
+
|
20
|
+
### Create Pages and HTML
|
21
|
+
|
22
|
+
First, create a form and handler:
|
23
|
+
|
24
|
+
```
|
25
|
+
bin/scaffold form /new_widget
|
26
|
+
```
|
27
|
+
|
28
|
+
Edit `app/src/front_end/forms/new_widget_form.rb`
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
class NewWidgetForm < AppForm
|
32
|
+
input :name, minlength: 3
|
33
|
+
input :description
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
Now, implement the handler in `app/src/front_end/handlers/new_widget_handler.rb` to check for client-side violations *and* require that the description have at
|
38
|
+
least 5 words in it.
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
class NewWidgetHandler < AppHandler
|
42
|
+
def initialize(form:)
|
43
|
+
@form = form
|
44
|
+
end
|
45
|
+
|
46
|
+
def handle
|
47
|
+
if @form.valid?
|
48
|
+
if @form.description.split(/\s+/).length < 5
|
49
|
+
@form.server_side_constraint_violation(
|
50
|
+
input_name: :description,
|
51
|
+
key: :not_enough_words
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
if @form.constraint_violations?
|
56
|
+
NewWidgetPage.new(form: @form)
|
57
|
+
else
|
58
|
+
redirect_to(HomePage)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
Add the new error message to `app/config/i18n/en/2_app.rb`
|
65
|
+
|
66
|
+
```ruby {6}
|
67
|
+
{
|
68
|
+
en: {
|
69
|
+
nevermind: "Nevermind",
|
70
|
+
cv: {
|
71
|
+
ss: {
|
72
|
+
not_enough_words: "%{field} must have at least %{minwords} words",
|
73
|
+
},
|
74
|
+
# ...
|
75
|
+
```
|
76
|
+
|
77
|
+
Now, build a minimal `NewWidgetPage`:
|
78
|
+
|
79
|
+
```
|
80
|
+
bin/scaffold page /new_widget`
|
81
|
+
```
|
82
|
+
|
83
|
+
We'll create the bare minimum in `app/src/front_end/pages/new_widget_page.rb`
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
class NewWidgetPage < AppPage
|
87
|
+
def initialize(form: nil)
|
88
|
+
@form = form || NewWidgetForm.new
|
89
|
+
end
|
90
|
+
|
91
|
+
def page_template
|
92
|
+
brut_form do
|
93
|
+
FormTag(for: @form) do
|
94
|
+
label do
|
95
|
+
Inputs::InputTag(form: @form, input_name: :name)
|
96
|
+
ConstraintViolations(form: @form, input_name: :name)
|
97
|
+
span { "Name" }
|
98
|
+
end
|
99
|
+
label do
|
100
|
+
Inputs::TextareaTag(form: @form, input_name: :name)
|
101
|
+
ConstraintViolations(form: @form, input_name: :name)
|
102
|
+
span { "Description" }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
### Create CSS
|
111
|
+
|
112
|
+
The most minimal CSS would be as followed, which you can place in `app/src/front_end/css/index.css`
|
113
|
+
|
114
|
+
```css
|
115
|
+
brut-cv {
|
116
|
+
display: none;
|
117
|
+
}
|
118
|
+
|
119
|
+
brut-form[submitted-invalid] brut-cv,
|
120
|
+
brut-cv[server-generated] {
|
121
|
+
display: block;
|
122
|
+
color: red;
|
123
|
+
}
|
124
|
+
```
|
125
|
+
|
126
|
+
This will show the messages in `<brut-cv>` *only* if:
|
127
|
+
|
128
|
+
* Form submission was attempted (`submitted-invalid` would be set on `<brut-form>`)
|
129
|
+
* The server generated via `ConstraintViolations` (only if the handler was triggered)
|
130
|
+
|
131
|
+
Because more than one `<brut-cv>` could be generated or inserted, you may want to style the
|
132
|
+
`<brut-cv-messages>` that contains them, but you only want it to show up if it has contents.
|
133
|
+
|
134
|
+
You can't just do `brut-cv-messages:has(brut-cv)`, because your container would show up before the form
|
135
|
+
submission was attempted.
|
136
|
+
|
137
|
+
Here is what you want instead. This will put the error messages in a red box:
|
138
|
+
|
139
|
+
```css
|
140
|
+
brut-form[submitted-invalid] brut-cv-messages:has(brut-cv),
|
141
|
+
brut-cv-messages:has(brut-cv[server-generated]) {
|
142
|
+
color: red;
|
143
|
+
background-color: pink;
|
144
|
+
border: solid thin red;
|
145
|
+
border-radius: 1rem;
|
146
|
+
}
|
147
|
+
```
|
148
|
+
|
data/docs/404.html
CHANGED
@@ -9,7 +9,7 @@
|
|
9
9
|
<link rel="preload stylesheet" href="/assets/style.B1z60PPQ.css" as="style">
|
10
10
|
<link rel="preload stylesheet" href="/vp-icons.css" as="style">
|
11
11
|
|
12
|
-
<script type="module" src="/assets/app.
|
12
|
+
<script type="module" src="/assets/app.vjGWMSnJ.js"></script>
|
13
13
|
<link rel="icon" href="/favicon.ico">
|
14
14
|
<meta property="og:title" content="BrutRB Documentation">
|
15
15
|
<meta property="og:type" content="website">
|
@@ -20,7 +20,7 @@
|
|
20
20
|
</head>
|
21
21
|
<body>
|
22
22
|
<div id="app"></div>
|
23
|
-
<script>window.__VP_HASH_MAP__=JSON.parse("{\"adrs.md\":\"BxjHi9-8\",\"ai.md\":\"Cy9GWnER\",\"assets.md\":\"7C3HWkga\",\"brut-js.md\":\"B4GYxQVw\",\"business-logic.md\":\"BY4hGy0m\",\"cli.md\":\"CjsktgFz\",\"components.md\":\"
|
23
|
+
<script>window.__VP_HASH_MAP__=JSON.parse("{\"adrs.md\":\"BxjHi9-8\",\"ai.md\":\"Cy9GWnER\",\"assets.md\":\"7C3HWkga\",\"brut-js.md\":\"B4GYxQVw\",\"business-logic.md\":\"BY4hGy0m\",\"cli.md\":\"CjsktgFz\",\"components.md\":\"B543a3Lm\",\"configuration.md\":\"-foE_iVv\",\"css.md\":\"CltvJqAa\",\"custom-element-tests.md\":\"B_rbta32\",\"database-access.md\":\"gnluu54N\",\"database-schema.md\":\"LpmBPVEU\",\"deployment.md\":\"BLseERGV\",\"dev-environment.md\":\"DRH2D2-O\",\"dir-structure.md\":\"CWir1pic\",\"doc-conventions.md\":\"DOkAuXlt\",\"end-to-end-tests.md\":\"DzqRpZ43\",\"features.md\":\"DPFXsy0z\",\"flash-and-session.md\":\"nPvUpnUx\",\"form-constraints.md\":\"DK5adCgM\",\"forms.md\":\"D5-2rgHh\",\"getting-started.md\":\"Cd4XSZb_\",\"handlers.md\":\"h84MMB1R\",\"hooks.md\":\"Jmb5VOLA\",\"i18n.md\":\"BAm9t9JJ\",\"index.md\":\"Bn9e0sRJ\",\"instrumentation.md\":\"BgcaGVYH\",\"javascript.md\":\"DzrMxUmI\",\"jobs.md\":\"S-2amAYp\",\"keyword-injection.md\":\"95Zgh2eN\",\"layouts.md\":\"CVGl9xIO\",\"lsp.md\":\"Dn1rIiW0\",\"markdown-examples.md\":\"CCFEQO44\",\"middleware.md\":\"Czz_UlJN\",\"overview.md\":\"DlKiRRG_\",\"pages.md\":\"B7Hc-i6H\",\"recipes_alternate-layouts.md\":\"BwEytl59\",\"recipes_authentication.md\":\"Dzvi_g69\",\"recipes_blank-layouts.md\":\"fyAUJyJR\",\"recipes_custom-flash.md\":\"CrQbI5eH\",\"recipes_indexed-forms.md\":\"CstYyOSo\",\"recipes_migrations.md\":\"CTcnWDJF\",\"recipes_text-field-component.md\":\"H4wLAK0Z\",\"roadmap.md\":\"C6PRi0DX\",\"routes.md\":\"BD6y2i-f\",\"security.md\":\"C0G_AZR-\",\"seed-data.md\":\"BvFZlqIk\",\"space-time-continuum.md\":\"xl44xDos\",\"tutorial.md\":\"C4zR5XPG\",\"unit-tests.md\":\"DUGrnLj5\",\"why.md\":\"C-hk5xgJ\"}");window.__VP_SITE_DATA__=JSON.parse("{\"lang\":\"en-US\",\"dir\":\"ltr\",\"title\":\"Brut RB\",\"description\":\"Documentation for the Brut.RB web framework.\",\"base\":\"/\",\"head\":[],\"router\":{\"prefetchLinks\":true},\"appearance\":true,\"themeConfig\":{\"search\":{\"provider\":\"local\"},\"nav\":[{\"text\":\"Home\",\"link\":\"/\"},{\"text\":\"Getting Started\",\"link\":\"/getting-started\"},{\"text\":\"Overview\",\"link\":\"/overview\"},{\"text\":\"Brut API\",\"link\":\"/api/index.html\",\"target\":\"_self\"},{\"text\":\"BrutJS\",\"link\":\"/brut-js/api/index.html\",\"target\":\"_self\"},{\"text\":\"BrutCSS\",\"link\":\"/brut-css/index.html\",\"target\":\"_self\"}],\"outline\":[2,3],\"sidebar\":[{\"text\":\"Overview\",\"collapsed\":false,\"items\":[{\"text\":\"Getting Started\",\"link\":\"/getting-started\"},{\"text\":\"Concepts\",\"link\":\"/overview\"},{\"text\":\"Features\",\"link\":\"/features\"},{\"text\":\"Directory Structure\",\"link\":\"/dir-structure\"},{\"text\":\"Dev Environment\",\"link\":\"/dev-environment\"},{\"text\":\"Tutorial\",\"link\":\"/tutorial\"},{\"text\":\"Documentation Conventions\",\"link\":\"/doc-conventions\"}]},{\"text\":\"Front-End\",\"collapsed\":false,\"items\":[{\"text\":\"Routes\",\"link\":\"/routes\"},{\"text\":\"Pages\",\"link\":\"/pages\"},{\"text\":\"Layouts\",\"link\":\"/layouts\"},{\"text\":\"Forms\",\"link\":\"/forms\"},{\"text\":\"Form Constraints\",\"link\":\"/form-constraints\"},{\"text\":\"Handlers and Actions\",\"link\":\"/handlers\"},{\"text\":\"Components\",\"link\":\"/components\"},{\"text\":\"Flash and Session\",\"link\":\"/flash-and-session\"},{\"text\":\"Space/Time Continuum\",\"link\":\"/space-time-continuum\"},{\"text\":\"JavaScript\",\"link\":\"/javascript\"},{\"text\":\"CSS\",\"link\":\"/css\"},{\"text\":\"Assets\",\"link\":\"/assets\"},{\"text\":\"BrutJS\",\"link\":\"/brut-js\"}]},{\"text\":\"Back-End\",\"collapsed\":false,\"items\":[{\"text\":\"Database Schema\",\"link\":\"/database-schema\"},{\"text\":\"Database Access\",\"link\":\"/database-access\"},{\"text\":\"Seed Data\",\"link\":\"/seed-data\"},{\"text\":\"Jobs\",\"link\":\"/jobs\"},{\"text\":\"Business Logic\",\"link\":\"/business-logic\"}]},{\"text\":\"Framework\",\"collapsed\":false,\"items\":[{\"text\":\"Configuration\",\"link\":\"/configuration\"},{\"text\":\"Keyword Injection\",\"link\":\"/keyword-injection\"},{\"text\":\"I18n\",\"link\":\"/i18n\"},{\"text\":\"CLI / Tasks\",\"link\":\"/cli\"},{\"text\":\"Deployment\",\"link\":\"/deployment\"}]},{\"text\":\"Testing\",\"collapsed\":false,\"items\":[{\"text\":\"Unit Tests\",\"link\":\"/unit-tests\"},{\"text\":\"End-to-End Tests\",\"link\":\"/end-to-end-tests\"},{\"text\":\"Testing Custom Elements\",\"link\":\"/custom-element-tests\"}]},{\"text\":\"Advanced Topics\",\"collapsed\":true,\"items\":[{\"text\":\"Route Hooks\",\"link\":\"/hooks\"},{\"text\":\"Middleware\",\"link\":\"/middleware\"},{\"text\":\"Instrumentation\",\"link\":\"/instrumentation\"},{\"text\":\"Security\",\"link\":\"/security\"},{\"text\":\"LSP Support\",\"link\":\"/lsp\"}]},{\"text\":\"Recipes\",\"collapsed\":true,\"items\":[{\"text\":\"Migration Basics\",\"link\":\"/recipes/migrations\"},{\"text\":\"Authentication\",\"link\":\"/recipes/authentication\"},{\"text\":\"Alternate Layouts\",\"link\":\"/recipes/alternate-layouts\"},{\"text\":\"Blank Layouts\",\"link\":\"/recipes/blank-layouts\"},{\"text\":\"Custom Flash Class\",\"link\":\"/recipes/custom-flash\"},{\"text\":\"Indexed Form Elements\",\"link\":\"/recipes/indexed-forms\"},{\"text\":\"Text Field Component\",\"link\":\"/recipes/text-field-component\"}]},{\"text\":\"Meta\",\"collapsed\":false,\"items\":[{\"text\":\"Why?!\",\"link\":\"/why\"},{\"text\":\"ADRs\",\"link\":\"/adrs\"},{\"text\":\"Roadmap to 1.0\",\"link\":\"/roadmap\"},{\"text\":\"AI Declaration\",\"link\":\"/ai\"}]}],\"socialLinks\":[{\"icon\":\"github\",\"link\":\"https://github.com/thirdtank/brut\"}]},\"locales\":{},\"scrollOffset\":134,\"cleanUrls\":false}");</script>
|
24
24
|
|
25
25
|
</body>
|
26
26
|
</html>
|