brut 0.0.20 → 0.0.22
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/.gitignore +24 -3
- data/.nvim.lua +1 -0
- data/Dockerfile.dx +12 -3
- data/Gemfile.lock +9 -7
- data/README.md +0 -7
- data/Rakefile +6 -4
- data/bin/dev +20 -0
- data/bin/docs +27 -0
- data/bin/setup +47 -1
- data/brut-css/.nvim.lua +1 -0
- data/brut-css/README.md +28 -0
- data/brut-css/bin/build +31 -0
- data/brut-css/bin/dev +1 -0
- data/brut-css/bin/docs +15 -0
- data/brut-css/bin/setup +5 -0
- data/brut-css/config/media-queries-all.css +15 -0
- data/brut-css/config/media-queries-minimal.css +5 -0
- data/brut-css/config/postcss.config.cjs +7 -0
- data/brut-css/config/pseudo-classes-all.css +9 -0
- data/brut-css/dx +1 -0
- data/brut-css/package-lock.json +3217 -0
- data/brut-css/package.json +36 -0
- data/brut-css/src/css/appearance.css +145 -0
- data/brut-css/src/css/border.css +522 -0
- data/brut-css/src/css/colors.css +3502 -0
- data/brut-css/src/css/dimensions.css +548 -0
- data/brut-css/src/css/flex.css +179 -0
- data/brut-css/src/css/index.css +13 -0
- data/brut-css/src/css/layout.css +120 -0
- data/brut-css/src/css/list.css +41 -0
- data/brut-css/src/css/positioning.css +354 -0
- data/brut-css/src/css/properties/colors.css +455 -0
- data/brut-css/src/css/properties/index.css +3 -0
- data/brut-css/src/css/properties/spacing.css +140 -0
- data/brut-css/src/css/properties/typography.css +224 -0
- data/brut-css/src/css/reset.css +107 -0
- data/brut-css/src/css/spacing.css +585 -0
- data/brut-css/src/css/typography.css +519 -0
- data/brut-css/src/css/utils.css +104 -0
- data/brut-css/src/docs/1_getting-started/1_overview.md +46 -0
- data/brut-css/src/docs/1_getting-started/2_installation.md +25 -0
- data/brut-css/src/docs/1_getting-started/3_core-concepts.md +75 -0
- data/brut-css/src/docs/1_getting-started/4_simple-example.md +132 -0
- data/brut-css/src/docs/1_getting-started/page.html.ejs +10 -0
- data/brut-css/src/docs/2_properties/page.html.ejs +71 -0
- data/brut-css/src/docs/3_classes/color-demo.html.ejs +31 -0
- data/brut-css/src/docs/3_classes/page.html.ejs +87 -0
- data/brut-css/src/docs/4_customization/1_design-system.md +36 -0
- data/brut-css/src/docs/4_customization/2_breakpoints.md +75 -0
- data/brut-css/src/docs/4_customization/3_pseudo-classes.md +74 -0
- data/brut-css/src/docs/4_customization/4_advanced-configuration.md +40 -0
- data/brut-css/src/docs/4_customization/page.html.ejs +10 -0
- data/brut-css/src/docs/docs.css +98 -0
- data/brut-css/src/docs/includes/body-and-header.html.ejs +30 -0
- data/brut-css/src/docs/includes/footer-and-rest.html.ejs +9 -0
- data/brut-css/src/docs/includes/head.html.ejs +5 -0
- data/brut-css/src/docs/includes/nav.html.ejs +10 -0
- data/brut-css/src/docs/index.html.ejs +32 -0
- data/brut-css/src/docs/prism-twilight.min.css +1 -0
- data/brut-css/src/js/Logger.js +71 -0
- data/brut-css/src/js/build.js +111 -0
- data/brut-css/src/js/cli/CLIArgError.js +7 -0
- data/brut-css/src/js/cli/Debug.js +27 -0
- data/brut-css/src/js/cli/DocsDir.js +16 -0
- data/brut-css/src/js/cli/DocsTemplateSourceDir.js +16 -0
- data/brut-css/src/js/cli/InputFile.js +31 -0
- data/brut-css/src/js/cli/MediaQueryConfigFile.js +10 -0
- data/brut-css/src/js/cli/OutputFile.js +22 -0
- data/brut-css/src/js/cli/ParsedArg.js +17 -0
- data/brut-css/src/js/cli/PathToBrutCSSRoot.js +19 -0
- data/brut-css/src/js/cli/PseudoClassConfigFile.js +11 -0
- data/brut-css/src/js/cli.js +108 -0
- data/brut-css/src/js/docGenerator.js +467 -0
- data/brut-css/src/js/mediaQueryConfigParser.js +98 -0
- data/brut-css/src/js/post-css-plugins/addMediaQueriesPlugin.js +49 -0
- data/brut-css/src/js/post-css-plugins/addPseudoClassesPlugin.js +42 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Category.js +9 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/DocState.js +185 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Documentable.js +8 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Group.js +7 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/ParsedComment.js +73 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Property.js +9 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyCategory.js +4 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/PropertyGroup.js +8 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/Rule.js +12 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleCategory.js +4 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/RuleGroup.js +8 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeRef.js +5 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin/SeeURL.js +9 -0
- data/brut-css/src/js/post-css-plugins/generateDocumentationPlugin.js +49 -0
- data/brut-css/src/js/post-css-plugins/generateRootCustomPropertiesPlugin.js +45 -0
- data/brut-css/src/js/pseudoClassConfigParser.js +145 -0
- data/brut-js/.projections.json +10 -0
- data/brut-js/README.md +118 -0
- data/brut-js/bin/build +10 -0
- data/brut-js/bin/ci +5 -0
- data/brut-js/bin/setup +5 -0
- data/brut-js/docs/README.md +8 -0
- data/brut-js/docs/jsdoc-plugins/customElementTag.js +8 -0
- data/brut-js/docs/jsdoc-theme/publish.js +692 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/linenumber.js +25 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/prettify/Apache-License-2.0.txt +202 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/prettify/lang-css.js +2 -0
- data/brut-js/docs/jsdoc-theme/static/scripts/prettify/prettify.js +28 -0
- data/brut-js/docs/jsdoc-theme/static/styles/jsdoc-default.css +327 -0
- data/brut-js/docs/jsdoc-theme/static/styles/prettify-jsdoc.css +111 -0
- data/brut-js/docs/jsdoc-theme/static/styles/prettify-tomorrow.css +132 -0
- data/brut-js/docs/jsdoc-theme/tmpl/augments.tmpl +10 -0
- data/brut-js/docs/jsdoc-theme/tmpl/container.tmpl +199 -0
- data/brut-js/docs/jsdoc-theme/tmpl/details.tmpl +143 -0
- data/brut-js/docs/jsdoc-theme/tmpl/example.tmpl +2 -0
- data/brut-js/docs/jsdoc-theme/tmpl/examples.tmpl +13 -0
- data/brut-js/docs/jsdoc-theme/tmpl/exceptions.tmpl +32 -0
- data/brut-js/docs/jsdoc-theme/tmpl/layout.tmpl +38 -0
- data/brut-js/docs/jsdoc-theme/tmpl/mainpage.tmpl +14 -0
- data/brut-js/docs/jsdoc-theme/tmpl/members.tmpl +38 -0
- data/brut-js/docs/jsdoc-theme/tmpl/method.tmpl +131 -0
- data/brut-js/docs/jsdoc-theme/tmpl/modifies.tmpl +14 -0
- data/brut-js/docs/jsdoc-theme/tmpl/params.tmpl +131 -0
- data/brut-js/docs/jsdoc-theme/tmpl/properties.tmpl +108 -0
- data/brut-js/docs/jsdoc-theme/tmpl/returns.tmpl +19 -0
- data/brut-js/docs/jsdoc-theme/tmpl/source.tmpl +8 -0
- data/brut-js/docs/jsdoc-theme/tmpl/tutorial.tmpl +19 -0
- data/brut-js/docs/jsdoc-theme/tmpl/type.tmpl +7 -0
- data/brut-js/docs/jsdoc.config.json +23 -0
- data/brut-js/docs/package-lock.json +343 -0
- data/brut-js/docs/package.json +7 -0
- data/brut-js/package-lock.json +2171 -0
- data/brut-js/package.json +32 -0
- data/brut-js/specs/AjaxSubmit.spec.js +256 -0
- data/brut-js/specs/Autosubmit.spec.js +127 -0
- data/brut-js/specs/ConfirmSubmit.spec.js +193 -0
- data/brut-js/specs/ConstraintViolationMessage.spec.js +33 -0
- data/brut-js/specs/ConstraintViolationMessages.spec.js +29 -0
- data/brut-js/specs/CopyToClipboard.spec.js +35 -0
- data/brut-js/specs/Form.spec.js +181 -0
- data/brut-js/specs/I18nTranslation.spec.js +19 -0
- data/brut-js/specs/LocaleDetection.spec.js +22 -0
- data/brut-js/specs/Message.spec.js +15 -0
- data/brut-js/specs/SpecHelper.js +23 -0
- data/brut-js/specs/Tabs.spec.js +41 -0
- data/brut-js/specs/config/asset_metadata.json +7 -0
- data/brut-js/src/AjaxSubmit.js +384 -0
- data/brut-js/src/Autosubmit.js +63 -0
- data/brut-js/src/BaseCustomElement.js +261 -0
- data/brut-js/src/ConfirmSubmit.js +116 -0
- data/brut-js/src/ConfirmationDialog.js +143 -0
- data/brut-js/src/ConstraintViolationMessage.js +125 -0
- data/brut-js/src/ConstraintViolationMessages.js +98 -0
- data/brut-js/src/CopyToClipboard.js +96 -0
- data/brut-js/src/Form.js +151 -0
- data/brut-js/src/I18nTranslation.js +61 -0
- data/brut-js/src/LocaleDetection.js +117 -0
- data/brut-js/src/Logger.js +90 -0
- data/brut-js/src/Message.js +56 -0
- data/brut-js/src/RichString.js +113 -0
- data/brut-js/src/Tabs.js +168 -0
- data/brut-js/src/Tracing.js +247 -0
- data/brut-js/src/appForTestingOnly.js +15 -0
- data/brut-js/src/index.js +130 -0
- data/brut-js/src/testing/AssetMetadata.js +35 -0
- data/brut-js/src/testing/AssetMetadataLoader.js +25 -0
- data/brut-js/src/testing/CustomElementTest.js +235 -0
- data/brut-js/src/testing/DOMCreator.js +45 -0
- data/brut-js/src/testing/index.js +48 -0
- data/brutrb.com/.vitepress/config.mjs +106 -0
- data/brutrb.com/.vitepress/plugins/jsdocLinker.js +34 -0
- data/brutrb.com/.vitepress/plugins/rdocLinker.js +18 -0
- data/brutrb.com/.vitepress/theme/custom.css +7 -0
- data/brutrb.com/.vitepress/theme/index.js +18 -0
- data/brutrb.com/.vitepress/theme/style.css +149 -0
- data/brutrb.com/ai.md +68 -0
- data/brutrb.com/assets.md +138 -0
- data/brutrb.com/bin/build +5 -0
- data/brutrb.com/bin/deploy +7 -0
- data/brutrb.com/bin/dev +5 -0
- data/brutrb.com/bin/setup +5 -0
- data/brutrb.com/brut-js.md +117 -0
- data/brutrb.com/business-logic.md +55 -0
- data/brutrb.com/cli.md +278 -0
- data/brutrb.com/components.md +243 -0
- data/brutrb.com/configuration.md +257 -0
- data/brutrb.com/css.md +103 -0
- data/brutrb.com/custom-element-tests.md +149 -0
- data/brutrb.com/database-access.md +201 -0
- data/brutrb.com/database-schema.md +312 -0
- data/brutrb.com/deployment.md +66 -0
- data/brutrb.com/dev-environment.md +179 -0
- data/brutrb.com/doc-conventions.md +39 -0
- data/brutrb.com/end-to-end-tests.md +174 -0
- data/brutrb.com/flash-and-session.md +224 -0
- data/brutrb.com/forms.md +866 -0
- data/brutrb.com/getting-started.md +66 -0
- data/brutrb.com/handlers.md +153 -0
- data/brutrb.com/hooks.md +178 -0
- data/brutrb.com/i18n.md +188 -0
- data/brutrb.com/images/Makefile +10 -0
- data/brutrb.com/images/dev-env-overview.dot +54 -0
- data/brutrb.com/images/dev-env-overview.png +0 -0
- data/brutrb.com/images/dev-env-protocol.dot +37 -0
- data/brutrb.com/images/dev-env-protocol.png +0 -0
- data/brutrb.com/images/logo-300.png +0 -0
- data/brutrb.com/images/logo.png +0 -0
- data/brutrb.com/images/overview.graffle +0 -0
- data/brutrb.com/images/overview.png +0 -0
- data/brutrb.com/images/spa.dot +19 -0
- data/brutrb.com/images/spa.png +0 -0
- data/brutrb.com/images/workspace-protocol.dot +44 -0
- data/brutrb.com/images/workspace-protocol.png +0 -0
- data/brutrb.com/index.md +36 -0
- data/brutrb.com/instrumentation.md +183 -0
- data/brutrb.com/javascript.md +122 -0
- data/brutrb.com/jobs.md +14 -0
- data/{doc-src → brutrb.com}/keyword-injection.md +122 -68
- data/brutrb.com/markdown-examples.md +85 -0
- data/brutrb.com/middleware.md +80 -0
- data/brutrb.com/not-released.md +5 -0
- data/brutrb.com/overview.md +404 -0
- data/brutrb.com/package-lock.json +2404 -0
- data/brutrb.com/package.json +11 -0
- data/brutrb.com/pages.md +378 -0
- data/brutrb.com/public/images/logo-300.png +0 -0
- data/brutrb.com/public/images/logo.png +0 -0
- data/brutrb.com/routes.md +215 -0
- data/brutrb.com/security.md +105 -0
- data/brutrb.com/seed-data.md +63 -0
- data/brutrb.com/space-time-continuum.md +85 -0
- data/brutrb.com/tutorial.md +3 -0
- data/brutrb.com/unit-tests.md +148 -0
- data/docker-compose.dx.yml +6 -3
- data/docs/404.html +21 -0
- data/docs/CNAME +1 -0
- data/docs/ai.html +24 -0
- data/docs/api/Brut/BackEnd/SeedData.html +493 -0
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +214 -0
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +125 -0
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +125 -0
- data/docs/api/Brut/BackEnd/Sidekiq.html +125 -0
- data/docs/api/Brut/BackEnd/Validators/FormValidator.html +414 -0
- data/docs/api/Brut/BackEnd/Validators.html +128 -0
- data/docs/api/Brut/BackEnd.html +132 -0
- data/docs/api/Brut/CLI/App.html +1576 -0
- data/docs/api/Brut/CLI/AppRunner.html +491 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +264 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +306 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +262 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +314 -0
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +183 -0
- data/docs/api/Brut/CLI/Apps/DB/Create.html +365 -0
- data/docs/api/Brut/CLI/Apps/DB/Drop.html +357 -0
- data/docs/api/Brut/CLI/Apps/DB/Migrate.html +383 -0
- data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +335 -0
- data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +329 -0
- data/docs/api/Brut/CLI/Apps/DB/Seed.html +347 -0
- data/docs/api/Brut/CLI/Apps/DB/Status.html +383 -0
- data/docs/api/Brut/CLI/Apps/DB.html +183 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +303 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +512 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +398 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +374 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +410 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +262 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +303 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +480 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +450 -0
- data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +380 -0
- data/docs/api/Brut/CLI/Apps/Scaffold.html +253 -0
- data/docs/api/Brut/CLI/Apps/Test/Audit.html +464 -0
- data/docs/api/Brut/CLI/Apps/Test/E2e.html +407 -0
- data/docs/api/Brut/CLI/Apps/Test/JS.html +262 -0
- data/docs/api/Brut/CLI/Apps/Test/Run.html +578 -0
- data/docs/api/Brut/CLI/Apps/Test.html +253 -0
- data/docs/api/Brut/CLI/Apps.html +125 -0
- data/docs/api/Brut/CLI/Command.html +2342 -0
- data/docs/api/Brut/CLI/Error.html +139 -0
- data/docs/api/Brut/CLI/ExecutionResults/Result.html +664 -0
- data/docs/api/Brut/CLI/ExecutionResults.html +675 -0
- data/docs/api/Brut/CLI/Executor.html +430 -0
- data/docs/api/Brut/CLI/InvalidOption.html +245 -0
- data/docs/api/Brut/CLI/Options.html +753 -0
- data/docs/api/Brut/CLI/Output.html +699 -0
- data/docs/api/Brut/CLI/SystemExecError.html +451 -0
- data/docs/api/Brut/CLI.html +263 -0
- data/docs/api/Brut/FactoryBot.html +225 -0
- data/docs/api/Brut/Framework/App.html +1097 -0
- data/docs/api/Brut/Framework/Config.html +1045 -0
- data/docs/api/Brut/Framework/Container.html +1379 -0
- data/docs/api/Brut/Framework/Error.html +140 -0
- data/docs/api/Brut/Framework/Errors/AbstractMethod.html +144 -0
- data/docs/api/Brut/Framework/Errors/Bug.html +234 -0
- data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +257 -0
- data/docs/api/Brut/Framework/Errors/MissingParameter.html +273 -0
- data/docs/api/Brut/Framework/Errors/NoClassForPath.html +471 -0
- data/docs/api/Brut/Framework/Errors/NotFound.html +308 -0
- data/docs/api/Brut/Framework/Errors/NotImplemented.html +234 -0
- data/docs/api/Brut/Framework/Errors.html +328 -0
- data/docs/api/Brut/Framework/FussyTypeEnforcement.html +392 -0
- data/docs/api/Brut/Framework/MCP.html +861 -0
- data/docs/api/Brut/Framework/ProjectEnvironment.html +648 -0
- data/docs/api/Brut/Framework.html +129 -0
- data/docs/api/Brut/FrontEnd/AssetPathResolver.html +317 -0
- data/docs/api/Brut/FrontEnd/Component/Helpers.html +326 -0
- data/docs/api/Brut/FrontEnd/Component.html +365 -0
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +470 -0
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +518 -0
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +317 -0
- data/docs/api/Brut/FrontEnd/Components/Input.html +195 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +339 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +660 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +417 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +918 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +651 -0
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +125 -0
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +367 -0
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +336 -0
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +655 -0
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +352 -0
- data/docs/api/Brut/FrontEnd/Components.html +135 -0
- data/docs/api/Brut/FrontEnd/Download.html +467 -0
- data/docs/api/Brut/FrontEnd/Flash.html +1150 -0
- data/docs/api/Brut/FrontEnd/Form.html +1157 -0
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +634 -0
- data/docs/api/Brut/FrontEnd/Forms/Input.html +615 -0
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +547 -0
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1318 -0
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +609 -0
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +587 -0
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +613 -0
- data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +582 -0
- data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +609 -0
- data/docs/api/Brut/FrontEnd/Forms.html +127 -0
- data/docs/api/Brut/FrontEnd/GenericResponse.html +377 -0
- data/docs/api/Brut/FrontEnd/Handler.html +442 -0
- data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +318 -0
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +336 -0
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +399 -0
- data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +354 -0
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +151 -0
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +315 -0
- data/docs/api/Brut/FrontEnd/Handlers.html +125 -0
- data/docs/api/Brut/FrontEnd/HandlingResults.html +339 -0
- data/docs/api/Brut/FrontEnd/HttpMethod.html +661 -0
- data/docs/api/Brut/FrontEnd/HttpStatus.html +496 -0
- data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +284 -0
- data/docs/api/Brut/FrontEnd/Layout.html +318 -0
- data/docs/api/Brut/FrontEnd/Middleware.html +135 -0
- data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +288 -0
- data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +292 -0
- data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +324 -0
- data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +372 -0
- data/docs/api/Brut/FrontEnd/Middlewares.html +125 -0
- data/docs/api/Brut/FrontEnd/Page.html +773 -0
- data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +797 -0
- data/docs/api/Brut/FrontEnd/Pages.html +125 -0
- data/docs/api/Brut/FrontEnd/RequestContext.html +1312 -0
- data/docs/api/Brut/FrontEnd/RouteHook.html +424 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +242 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +249 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +264 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +261 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +284 -0
- data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +252 -0
- data/docs/api/Brut/FrontEnd/RouteHooks.html +115 -0
- data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +227 -0
- data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +305 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +324 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +319 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +315 -0
- data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +315 -0
- data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +327 -0
- data/docs/api/Brut/FrontEnd/Routing/Route.html +761 -0
- data/docs/api/Brut/FrontEnd/Routing.html +927 -0
- data/docs/api/Brut/FrontEnd/Session.html +1195 -0
- data/docs/api/Brut/FrontEnd.html +134 -0
- data/docs/api/Brut/I18n/BaseMethods.html +931 -0
- data/docs/api/Brut/I18n/ForBackEnd.html +302 -0
- data/docs/api/Brut/I18n/ForCLI.html +302 -0
- data/docs/api/Brut/I18n/ForHTML.html +296 -0
- data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +316 -0
- data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +930 -0
- data/docs/api/Brut/I18n.html +127 -0
- data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +435 -0
- data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +286 -0
- data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +302 -0
- data/docs/api/Brut/Instrumentation/OpenTelemetry.html +864 -0
- data/docs/api/Brut/Instrumentation.html +126 -0
- data/docs/api/Brut/SinatraHelpers/ClassMethods.html +532 -0
- data/docs/api/Brut/SinatraHelpers.html +281 -0
- data/docs/api/Brut/SpecSupport/ClockSupport.html +383 -0
- data/docs/api/Brut/SpecSupport/ComponentSupport.html +502 -0
- data/docs/api/Brut/SpecSupport/E2ETestServer.html +503 -0
- data/docs/api/Brut/SpecSupport/E2eSupport.html +142 -0
- data/docs/api/Brut/SpecSupport/EnhancedNode.html +403 -0
- data/docs/api/Brut/SpecSupport/FlashSupport.html +278 -0
- data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +401 -0
- data/docs/api/Brut/SpecSupport/GeneralSupport.html +195 -0
- data/docs/api/Brut/SpecSupport/HandlerSupport.html +160 -0
- data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +553 -0
- data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +439 -0
- data/docs/api/Brut/SpecSupport/Matchers.html +125 -0
- data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +335 -0
- data/docs/api/Brut/SpecSupport/RSpecSetup.html +602 -0
- data/docs/api/Brut/SpecSupport/SessionSupport.html +196 -0
- data/docs/api/Brut/SpecSupport.html +129 -0
- data/docs/api/Brut.html +225 -0
- data/docs/api/Clock.html +603 -0
- data/docs/api/RichString.html +968 -0
- data/docs/api/SemanticLogger/Appender/Async.html +219 -0
- data/docs/api/Sequel/Extensions/BrutInstrumentation.html +115 -0
- data/docs/api/Sequel/Extensions/BrutMigrations.html +533 -0
- data/docs/api/Sequel/Extensions.html +117 -0
- data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +105 -0
- data/docs/api/Sequel/Plugins/CreatedAt.html +125 -0
- data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +207 -0
- data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +186 -0
- data/docs/api/Sequel/Plugins/ExternalId.html +218 -0
- data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +202 -0
- data/docs/api/Sequel/Plugins/FindBang.html +125 -0
- data/docs/api/Sequel/Plugins.html +117 -0
- data/docs/api/Sequel.html +117 -0
- data/docs/api/_index.html +1553 -0
- data/docs/api/class_list.html +54 -0
- data/docs/api/css/common.css +1 -0
- data/docs/api/css/full_list.css +58 -0
- data/docs/api/css/style.css +503 -0
- data/docs/api/file.README.html +127 -0
- data/docs/api/file_list.html +59 -0
- data/docs/api/frames.html +22 -0
- data/docs/api/index.html +127 -0
- data/docs/api/js/app.js +344 -0
- data/docs/api/js/full_list.js +242 -0
- data/docs/api/js/jquery.js +4 -0
- data/docs/api/method_list.html +3998 -0
- data/docs/api/top-level-namespace.html +112 -0
- data/docs/assets/ai.md.tZrjP9im.js +1 -0
- data/docs/assets/ai.md.tZrjP9im.lean.js +1 -0
- data/docs/assets/app.D_yaTITQ.js +1 -0
- data/docs/assets/assets.md.D3wunzLx.js +19 -0
- data/docs/assets/assets.md.D3wunzLx.lean.js +1 -0
- data/docs/assets/brut-js.md.o2DAO2s2.js +12 -0
- data/docs/assets/brut-js.md.o2DAO2s2.lean.js +1 -0
- data/docs/assets/business-logic.md.BY4hGy0m.js +1 -0
- data/docs/assets/business-logic.md.BY4hGy0m.lean.js +1 -0
- data/docs/assets/chunks/@localSearchIndexroot.BsN5i0Fi.js +1 -0
- data/docs/assets/chunks/VPLocalSearchBox.B2-ZzyTY.js +8 -0
- data/docs/assets/chunks/framework.1L-BeKqY.js +18 -0
- data/docs/assets/chunks/theme.CfGFVRvE.js +2 -0
- data/docs/assets/cli.md.RmeA2b0i.js +127 -0
- data/docs/assets/cli.md.RmeA2b0i.lean.js +1 -0
- data/docs/assets/components.md.eCttGlN-.js +104 -0
- data/docs/assets/components.md.eCttGlN-.lean.js +1 -0
- data/docs/assets/configuration.md.BRriU0cL.js +78 -0
- data/docs/assets/configuration.md.BRriU0cL.lean.js +1 -0
- data/docs/assets/css.md.DJgj2clw.js +21 -0
- data/docs/assets/css.md.DJgj2clw.lean.js +1 -0
- data/docs/assets/custom-element-tests.md.BrYJQEl3.js +69 -0
- data/docs/assets/custom-element-tests.md.BrYJQEl3.lean.js +1 -0
- data/docs/assets/database-access.md.C7l-Vuvb.js +63 -0
- data/docs/assets/database-access.md.C7l-Vuvb.lean.js +1 -0
- data/docs/assets/database-schema.md.BUjR0VS1.js +63 -0
- data/docs/assets/database-schema.md.BUjR0VS1.lean.js +1 -0
- data/docs/assets/deployment.md.Dbka4OTr.js +1 -0
- data/docs/assets/deployment.md.Dbka4OTr.lean.js +1 -0
- data/docs/assets/dev-env-overview.Gj7NWM8-.png +0 -0
- data/docs/assets/dev-env-protocol.DysDAtnz.png +0 -0
- data/docs/assets/dev-environment.md.BNc8AYiK.js +11 -0
- data/docs/assets/dev-environment.md.BNc8AYiK.lean.js +1 -0
- data/docs/assets/doc-conventions.md.DCfRXXi-.js +1 -0
- data/docs/assets/doc-conventions.md.DCfRXXi-.lean.js +1 -0
- data/docs/assets/end-to-end-tests.md.yfQHC0b5.js +26 -0
- data/docs/assets/end-to-end-tests.md.yfQHC0b5.lean.js +1 -0
- data/docs/assets/flash-and-session.md.BXY8RvT0.js +93 -0
- data/docs/assets/flash-and-session.md.BXY8RvT0.lean.js +1 -0
- data/docs/assets/forms.md.CBTYQ_Cz.js +379 -0
- data/docs/assets/forms.md.CBTYQ_Cz.lean.js +1 -0
- data/docs/assets/getting-started.md.Bz2s1Vjb.js +2 -0
- data/docs/assets/getting-started.md.Bz2s1Vjb.lean.js +1 -0
- data/docs/assets/handlers.md.089DVD3v.js +69 -0
- data/docs/assets/handlers.md.089DVD3v.lean.js +1 -0
- data/docs/assets/hooks.md.C4-moMny.js +80 -0
- data/docs/assets/hooks.md.C4-moMny.lean.js +1 -0
- data/docs/assets/i18n.md.Do9i1qWl.js +23 -0
- data/docs/assets/i18n.md.Do9i1qWl.lean.js +1 -0
- data/docs/assets/index.md.B28EwVpq.js +1 -0
- data/docs/assets/index.md.B28EwVpq.lean.js +1 -0
- data/docs/assets/instrumentation.md.CL6ax7nT.js +35 -0
- data/docs/assets/instrumentation.md.CL6ax7nT.lean.js +1 -0
- data/docs/assets/javascript.md.GWbhRS51.js +31 -0
- data/docs/assets/javascript.md.GWbhRS51.lean.js +1 -0
- data/docs/assets/jobs.md.S-2amAYp.js +1 -0
- data/docs/assets/jobs.md.S-2amAYp.lean.js +1 -0
- data/docs/assets/keyword-injection.md.Dt2tKREs.js +25 -0
- data/docs/assets/keyword-injection.md.Dt2tKREs.lean.js +1 -0
- data/docs/assets/markdown-examples.md.CCFEQO44.js +33 -0
- data/docs/assets/markdown-examples.md.CCFEQO44.lean.js +1 -0
- data/docs/assets/middleware.md.Czz_UlJN.js +20 -0
- data/docs/assets/middleware.md.Czz_UlJN.lean.js +1 -0
- data/docs/assets/not-released.md.BBy28McC.js +1 -0
- data/docs/assets/not-released.md.BBy28McC.lean.js +1 -0
- data/docs/assets/overview.Da81cB9R.png +0 -0
- data/docs/assets/overview.md.CDalkuxV.js +133 -0
- data/docs/assets/overview.md.CDalkuxV.lean.js +1 -0
- data/docs/assets/pages.md.BE3kfOc5.js +122 -0
- data/docs/assets/pages.md.BE3kfOc5.lean.js +1 -0
- data/docs/assets/routes.md.BMM7peut.js +29 -0
- data/docs/assets/routes.md.BMM7peut.lean.js +1 -0
- data/docs/assets/security.md.C668yXCi.js +1 -0
- data/docs/assets/security.md.C668yXCi.lean.js +1 -0
- data/docs/assets/seed-data.md.BvFZlqIk.js +14 -0
- data/docs/assets/seed-data.md.BvFZlqIk.lean.js +1 -0
- data/docs/assets/spa.qejUdp-5.png +0 -0
- data/docs/assets/space-time-continuum.md.KPUIKysQ.js +1 -0
- data/docs/assets/space-time-continuum.md.KPUIKysQ.lean.js +1 -0
- data/docs/assets/style.D73IYGCX.css +1 -0
- data/docs/assets/tutorial.md.BnoGjrdK.js +1 -0
- data/docs/assets/tutorial.md.BnoGjrdK.lean.js +1 -0
- data/docs/assets/unit-tests.md.DUGrnLj5.js +13 -0
- data/docs/assets/unit-tests.md.DUGrnLj5.lean.js +1 -0
- data/docs/assets/workspace-protocol.C0gXsoDb.png +0 -0
- data/docs/assets.html +42 -0
- data/docs/brut-css/brut.css +1 -0
- data/docs/brut-css/brut.max.css +22372 -0
- data/docs/brut-css/classes/appearances.html +783 -0
- data/docs/brut-css/classes/background-colors.html +3529 -0
- data/docs/brut-css/classes/border-colors.html +3529 -0
- data/docs/brut-css/classes/borders.html +2293 -0
- data/docs/brut-css/classes/dimensions.html +2581 -0
- data/docs/brut-css/classes/flex.html +917 -0
- data/docs/brut-css/classes/foreground-colors.html +3261 -0
- data/docs/brut-css/classes/junk-drawer.html +431 -0
- data/docs/brut-css/classes/layout.html +668 -0
- data/docs/brut-css/classes/lists.html +331 -0
- data/docs/brut-css/classes/positioning.html +1751 -0
- data/docs/brut-css/classes/spacings.html +2633 -0
- data/docs/brut-css/classes/typography.html +2206 -0
- data/docs/brut-css/customization/advanced-configuration.html +204 -0
- data/docs/brut-css/customization/breakpoints.html +227 -0
- data/docs/brut-css/customization/design-system.html +197 -0
- data/docs/brut-css/customization/pseudo-classes.html +228 -0
- data/docs/brut-css/docs.css +98 -0
- data/docs/brut-css/getting-started/core-concepts.html +234 -0
- data/docs/brut-css/getting-started/installation.html +190 -0
- data/docs/brut-css/getting-started/overview.html +210 -0
- data/docs/brut-css/getting-started/simple-example.html +285 -0
- data/docs/brut-css/index.html +193 -0
- data/docs/brut-css/prism-twilight.min.css +1 -0
- data/docs/brut-css/properties/colors.html +1548 -0
- data/docs/brut-css/properties/spacings.html +614 -0
- data/docs/brut-css/properties/typography.html +777 -0
- data/docs/brut-js/api/AjaxSubmit.html +374 -0
- data/docs/brut-js/api/AjaxSubmit.js.html +435 -0
- data/docs/brut-js/api/Autosubmit.html +192 -0
- data/docs/brut-js/api/Autosubmit.js.html +114 -0
- data/docs/brut-js/api/BaseCustomElement.html +1091 -0
- data/docs/brut-js/api/BaseCustomElement.js.html +312 -0
- data/docs/brut-js/api/BrutCustomElements.html +172 -0
- data/docs/brut-js/api/BufferedLogger.html +173 -0
- data/docs/brut-js/api/ConfirmSubmit.html +278 -0
- data/docs/brut-js/api/ConfirmSubmit.js.html +167 -0
- data/docs/brut-js/api/ConfirmationDialog.html +425 -0
- data/docs/brut-js/api/ConfirmationDialog.js.html +194 -0
- data/docs/brut-js/api/ConstraintViolationMessage.html +448 -0
- data/docs/brut-js/api/ConstraintViolationMessage.js.html +176 -0
- data/docs/brut-js/api/ConstraintViolationMessages.html +590 -0
- data/docs/brut-js/api/ConstraintViolationMessages.js.html +149 -0
- data/docs/brut-js/api/CopyToClipboard.html +345 -0
- data/docs/brut-js/api/CopyToClipboard.js.html +147 -0
- data/docs/brut-js/api/Form.html +294 -0
- data/docs/brut-js/api/Form.js.html +202 -0
- data/docs/brut-js/api/I18nTranslation.html +409 -0
- data/docs/brut-js/api/I18nTranslation.js.html +112 -0
- data/docs/brut-js/api/LocaleDetection.html +312 -0
- data/docs/brut-js/api/LocaleDetection.js.html +168 -0
- data/docs/brut-js/api/Logger.html +702 -0
- data/docs/brut-js/api/Logger.js.html +141 -0
- data/docs/brut-js/api/Message.html +238 -0
- data/docs/brut-js/api/Message.js.html +107 -0
- data/docs/brut-js/api/PrefixedLogger.html +369 -0
- data/docs/brut-js/api/RichString.html +1049 -0
- data/docs/brut-js/api/RichString.js.html +164 -0
- data/docs/brut-js/api/Tabs.html +295 -0
- data/docs/brut-js/api/Tabs.js.html +219 -0
- data/docs/brut-js/api/Tracing.html +277 -0
- data/docs/brut-js/api/Tracing.js.html +298 -0
- data/docs/brut-js/api/external-CustomElementRegistry.html +140 -0
- data/docs/brut-js/api/external-Performance.html +138 -0
- data/docs/brut-js/api/external-Promise.html +138 -0
- data/docs/brut-js/api/external-ValidityState.html +138 -0
- data/docs/brut-js/api/external-Window.html +233 -0
- data/docs/brut-js/api/external-fetch.html +138 -0
- data/docs/brut-js/api/global.html +400 -0
- data/docs/brut-js/api/index.html +168 -0
- data/docs/brut-js/api/index.js.html +181 -0
- data/docs/brut-js/api/module-testing.html +383 -0
- data/docs/brut-js/api/scripts/linenumber.js +25 -0
- data/docs/brut-js/api/scripts/prettify/Apache-License-2.0.txt +202 -0
- data/docs/brut-js/api/scripts/prettify/lang-css.js +2 -0
- data/docs/brut-js/api/scripts/prettify/prettify.js +28 -0
- data/docs/brut-js/api/styles/jsdoc-default.css +327 -0
- data/docs/brut-js/api/styles/prettify-jsdoc.css +111 -0
- data/docs/brut-js/api/styles/prettify-tomorrow.css +132 -0
- data/docs/brut-js/api/testing.AssetMetadata.html +172 -0
- data/docs/brut-js/api/testing.AssetMetadataLoader.html +171 -0
- data/docs/brut-js/api/testing.CustomElementTest.html +679 -0
- data/docs/brut-js/api/testing.DOMCreator.html +171 -0
- data/docs/brut-js/api/testing_AssetMetadata.js.html +86 -0
- data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +76 -0
- data/docs/brut-js/api/testing_CustomElementTest.js.html +286 -0
- data/docs/brut-js/api/testing_DOMCreator.js.html +96 -0
- data/docs/brut-js/api/testing_index.js.html +99 -0
- data/docs/brut-js.html +35 -0
- data/docs/business-logic.html +24 -0
- data/docs/cli.html +150 -0
- data/docs/components.html +127 -0
- data/docs/configuration.html +101 -0
- data/docs/css.html +44 -0
- data/docs/custom-element-tests.html +92 -0
- data/docs/database-access.html +86 -0
- data/docs/database-schema.html +86 -0
- data/docs/deployment.html +24 -0
- data/docs/dev-environment.html +34 -0
- data/docs/doc-conventions.html +24 -0
- data/docs/end-to-end-tests.html +49 -0
- data/docs/flash-and-session.html +116 -0
- data/docs/forms.html +402 -0
- data/docs/getting-started.html +25 -0
- data/docs/handlers.html +92 -0
- data/docs/hashmap.json +1 -0
- data/docs/hooks.html +103 -0
- data/docs/i18n.html +46 -0
- data/docs/images/logo-300.png +0 -0
- data/docs/images/logo.png +0 -0
- data/docs/index.html +24 -0
- data/docs/instrumentation.html +58 -0
- data/docs/javascript.html +54 -0
- data/docs/jobs.html +24 -0
- data/docs/keyword-injection.html +48 -0
- data/docs/markdown-examples.html +56 -0
- data/docs/middleware.html +43 -0
- data/docs/not-released.html +24 -0
- data/docs/overview.html +156 -0
- data/docs/pages.html +145 -0
- data/docs/routes.html +52 -0
- data/docs/security.html +24 -0
- data/docs/seed-data.html +37 -0
- data/docs/space-time-continuum.html +24 -0
- data/docs/tutorial.html +24 -0
- data/docs/unit-tests.html +36 -0
- data/docs/vp-icons.css +1 -0
- data/lib/brut/back_end/seed_data.rb +19 -2
- data/lib/brut/back_end/sidekiq/middlewares/server.rb +2 -1
- data/lib/brut/back_end/sidekiq/middlewares.rb +2 -1
- data/lib/brut/back_end/sidekiq.rb +2 -1
- data/lib/brut/back_end/validator.rb +5 -1
- data/lib/brut/back_end.rb +4 -2
- data/lib/brut/cli/app_runner.rb +1 -1
- data/lib/brut/cli/apps/test.rb +5 -0
- data/lib/brut/cli.rb +4 -3
- data/lib/brut/factory_bot.rb +0 -5
- data/lib/brut/framework/app.rb +70 -5
- data/lib/brut/framework/config.rb +5 -3
- data/lib/brut/framework/container.rb +3 -2
- data/lib/brut/framework/errors.rb +12 -4
- data/lib/brut/framework/mcp.rb +58 -1
- data/lib/brut/framework/project_environment.rb +6 -2
- data/lib/brut/framework.rb +1 -1
- data/lib/brut/front_end/component.rb +69 -71
- data/lib/brut/front_end/components/constraint_violations.rb +1 -4
- data/lib/brut/front_end/components/form_tag.rb +1 -1
- data/lib/brut/front_end/components/input.rb +3 -3
- data/lib/brut/front_end/components/inputs/csrf_token.rb +1 -1
- data/lib/brut/front_end/components/inputs/{text_field.rb → input_tag.rb} +7 -9
- data/lib/brut/front_end/components/inputs/radio_button.rb +1 -1
- data/lib/brut/front_end/components/inputs/select_tag_with_options.rb +187 -0
- data/lib/brut/front_end/components/inputs/{textarea.rb → textarea_tag.rb} +2 -2
- data/lib/brut/front_end/components/time_tag.rb +2 -1
- data/lib/brut/front_end/form.rb +4 -4
- data/lib/brut/front_end/forms/input.rb +2 -1
- data/lib/brut/front_end/forms/input_definition.rb +5 -2
- data/lib/brut/front_end/forms/radio_button_group_input.rb +2 -1
- data/lib/brut/front_end/forms/radio_button_group_input_definition.rb +2 -2
- data/lib/brut/front_end/forms/select_input.rb +2 -4
- data/lib/brut/front_end/forms/select_input_definition.rb +2 -2
- data/lib/brut/front_end/handler.rb +28 -26
- data/lib/brut/front_end/handlers/csp_reporting_handler.rb +5 -2
- data/lib/brut/front_end/handlers/instrumentation_handler.rb +8 -4
- data/lib/brut/front_end/handlers/locale_detection_handler.rb +9 -5
- data/lib/brut/front_end/handlers/missing_handler.rb +5 -2
- data/lib/brut/front_end/layout.rb +16 -0
- data/lib/brut/front_end/page.rb +52 -29
- data/lib/brut/front_end/request_context.rb +3 -2
- data/lib/brut/front_end/routing.rb +5 -1
- data/lib/brut/front_end.rb +4 -13
- data/lib/brut/i18n/base_methods.rb +167 -79
- data/lib/brut/i18n/for_back_end.rb +4 -0
- data/lib/brut/i18n/for_cli.rb +4 -0
- data/lib/brut/i18n/for_html.rb +32 -4
- data/lib/brut/i18n/http_accept_language.rb +47 -0
- data/lib/brut/instrumentation/open_telemetry.rb +36 -1
- data/lib/brut/instrumentation.rb +3 -5
- data/lib/brut/sinatra_helpers.rb +11 -3
- data/lib/brut/spec_support/component_support.rb +30 -16
- data/lib/brut/spec_support/e2e_support.rb +1 -1
- data/lib/brut/spec_support/e2e_test_server.rb +3 -0
- data/lib/brut/spec_support/general_support.rb +3 -0
- data/lib/brut/spec_support/handler_support.rb +6 -1
- data/lib/brut/spec_support/matcher.rb +1 -0
- data/lib/brut/spec_support/matchers/be_page_for.rb +1 -0
- data/lib/brut/spec_support/matchers/have_html_attribute.rb +1 -0
- data/lib/brut/spec_support/matchers/have_i18n_string.rb +2 -5
- data/lib/brut/spec_support/matchers/have_link_to.rb +1 -0
- data/lib/brut/spec_support/matchers/have_redirected_to.rb +1 -0
- data/lib/brut/spec_support/matchers/have_rendered.rb +1 -0
- data/lib/brut/spec_support/matchers/have_returned_rack_response.rb +44 -0
- data/lib/brut/spec_support.rb +1 -1
- data/lib/brut/version.rb +1 -1
- data/lib/brut.rb +5 -4
- data/lib/sequel/extensions/brut_migrations.rb +1 -1
- metadata +648 -13
- data/doc-src/architecture.md +0 -102
- data/doc-src/assets.md +0 -98
- data/doc-src/forms.md +0 -214
- data/doc-src/handlers.md +0 -83
- data/doc-src/javascript.md +0 -265
- data/doc-src/pages.md +0 -210
- data/doc-src/route-hooks.md +0 -59
- data/lib/brut/front_end/components/inputs/select.rb +0 -117
@@ -0,0 +1,261 @@
|
|
1
|
+
import Logger from "./Logger"
|
2
|
+
import RichString from "./RichString"
|
3
|
+
|
4
|
+
/** Base class for Custom Elements that provides a few quality-of-life enhancements.
|
5
|
+
*
|
6
|
+
* Custom elements that use this base class instead of `HTMLElement` get the following features:
|
7
|
+
*
|
8
|
+
* * `connectedCallback` and `attributeChangedCallback` call into a central `update` method where the
|
9
|
+
* class can centralilize its logic
|
10
|
+
* * Instead of implementing `attributeChangedCallback` and checking the name, per-attribute
|
11
|
+
* callbacks can be implemented that are called when an observed attribute is changed. See {@link
|
12
|
+
* attributeChangedCallback}.
|
13
|
+
* * Support for defining the element by declaring a tag name
|
14
|
+
* * Opt-in debugging support to allow verbose logging of mistaken use of the element that can be turned
|
15
|
+
* off for production use.
|
16
|
+
*
|
17
|
+
* How to use this class:
|
18
|
+
*
|
19
|
+
* 1. Your custom element should extend this class via `extends BaseCustomElement`
|
20
|
+
* 2. Create a static property called `tagName` that will be your element's tag name. Remember that all tag names must have a dash in them.
|
21
|
+
* 3. Create a static property called `observedAttributes` that is an array of attribute names your element supports. This is part of the HTML spec and not specific to this base class.
|
22
|
+
* 4. If you include the attribute `show-warnings` in your list of `observedAttributes`, you will have enhanced debugging abilities.
|
23
|
+
* 5. For each attribute *other* than `show-warnings`, implement a callback to receive notifications on the attribute's changes. See {@link attributeChangedCallback} for more info.
|
24
|
+
* 6. Implement `update` to execute whatever logic the component needs. `update` will be called multiple times and thus should be relatively idempotent. Specifically, it will be called after any attribute has changed, and it will be called as part of the standard `connectedCallback`.
|
25
|
+
* 7. To use your component, call the static {@link define} method.
|
26
|
+
*
|
27
|
+
* Debugging
|
28
|
+
*
|
29
|
+
* Custom Elements have to work under a variety of degenerate cirucmstances. Further, if you are building
|
30
|
+
* elements that wrap and enhance conventional elements, it can be easy to make a mistake, for example intending
|
31
|
+
* to wrap a `FORM`, but wrapping only an `INPUT`.
|
32
|
+
*
|
33
|
+
* To help debug these situations, you are encouraged to use `this.logger.warn(...)` to emit warnings when
|
34
|
+
* potentially incorrect use of your component is detected. By default, these warnings will not be shown. This
|
35
|
+
* provides your users with a drama-free console. During development, however, you can add the `show-warnings`
|
36
|
+
* attribute to your element. If that is set, warnings *are* shown in the console.
|
37
|
+
*
|
38
|
+
* `show-warnings` can be given a value, in which case that value if used to prefix all warnings the element emits.
|
39
|
+
* This can be useful to know which use of an element is causing problems. If you don't give any value
|
40
|
+
* to `show-warnings`, the element's `id` will be used as the prefix. If the element has no `id`, you will
|
41
|
+
* still see warnings, but without a prefix. This could make it hard to know where the warnings are coming from.
|
42
|
+
*
|
43
|
+
* @example
|
44
|
+
* // Replaces all span elements inside the component with
|
45
|
+
* // an upper-cased value of the attribute 'some-attribute'
|
46
|
+
* class MyComponent extends BaseCustomElement {
|
47
|
+
* static tagName = "my-component"
|
48
|
+
* static observedAttributes = [
|
49
|
+
* "show-warnings",
|
50
|
+
* "some-attribute",
|
51
|
+
* ]
|
52
|
+
*
|
53
|
+
* someAttributeChangedCallback({newValue}) {
|
54
|
+
* this.someAttribute = newValue ? newValue.toUpperCase() : null
|
55
|
+
* }
|
56
|
+
*
|
57
|
+
* update() {
|
58
|
+
* const spans = this.querySelectorAll("span")
|
59
|
+
* if (spans.length == 0) {
|
60
|
+
* this.logger.warn("Did not find any <span> elements - element won't do anything")
|
61
|
+
* }
|
62
|
+
* spans.forEach( (element) => {
|
63
|
+
* element.textContent = this.someAttribute
|
64
|
+
* })
|
65
|
+
* }
|
66
|
+
* }
|
67
|
+
* docment.addEventListener("DOMContentLoaded", () => {
|
68
|
+
* MyComponent.define()
|
69
|
+
* })
|
70
|
+
*
|
71
|
+
* // Then, in your HTML
|
72
|
+
* <my-component some-attribute="hello there">
|
73
|
+
* <span></span>
|
74
|
+
* <div></div>
|
75
|
+
* <span></span>
|
76
|
+
* </my-component>
|
77
|
+
*
|
78
|
+
* // The browser will effectively produce this HTML:
|
79
|
+
* <my-component some-attribute="hello there">
|
80
|
+
* <span>HELLO THERE</span>
|
81
|
+
* <div></div>
|
82
|
+
* <span>HELLO THERE</span>
|
83
|
+
* </my-component>
|
84
|
+
*
|
85
|
+
* // If JavaScript (or browser dev tools) changed some-attribute
|
86
|
+
* // to be "goodbye then", the markup will change to look like so:
|
87
|
+
* <my-component some-attribute="goodby then">
|
88
|
+
* <span>GOODBYE THEN</span>
|
89
|
+
* <div></div>
|
90
|
+
* <span>GOODBYE THEN</span>
|
91
|
+
* </my-component>
|
92
|
+
*/
|
93
|
+
class BaseCustomElement extends HTMLElement {
|
94
|
+
|
95
|
+
/** A {@link Logger} you can use to write warning messages. By default, these
|
96
|
+
* messages are not shown in the console. If you put `show-warnings` as an attribute on your
|
97
|
+
* element, warnings sent to this logger *are* shown.
|
98
|
+
*/
|
99
|
+
logger = Logger.forPrefix(null)
|
100
|
+
|
101
|
+
#_connectedCallbackCalled = false
|
102
|
+
#_disconnectedCallbackCalled = false
|
103
|
+
|
104
|
+
constructor() {
|
105
|
+
super()
|
106
|
+
this.logger = Logger.forPrefix(null)
|
107
|
+
}
|
108
|
+
|
109
|
+
/** You must call this to define the custom element. This is bascially
|
110
|
+
* a wrapper around `customElements.define`. It is recommended that you call
|
111
|
+
* this inside a `DOMContentLoaded` event, or after the page's HTML has been processed.
|
112
|
+
*
|
113
|
+
* @see external:CustomElementRegistry
|
114
|
+
*/
|
115
|
+
static define() {
|
116
|
+
if (!this.tagName) {
|
117
|
+
throw `To use BaseCustomElement, you must define the static member tagName to return your custom tag's name`
|
118
|
+
}
|
119
|
+
customElements.define(this.tagName, this)
|
120
|
+
}
|
121
|
+
|
122
|
+
showWarningsChangedCallback({oldValue,newValue}) {
|
123
|
+
let oldLogger
|
124
|
+
if (!oldValue && newValue) {
|
125
|
+
oldLogger = this.logger
|
126
|
+
}
|
127
|
+
let prefix = newValue == "" ? this.id : newValue
|
128
|
+
if (!prefix) {
|
129
|
+
prefix = "UNKNOWN COMPONENT"
|
130
|
+
}
|
131
|
+
this.logger = Logger.forPrefix(prefix)
|
132
|
+
if (oldLogger) {
|
133
|
+
this.logger.dump(oldLogger)
|
134
|
+
}
|
135
|
+
}
|
136
|
+
|
137
|
+
/**
|
138
|
+
* Overrides the standard callback to allow subclasses to have a slightly easier API when responding
|
139
|
+
* to attribute changes. You can override this to use the custom element callback directly. Note that if
|
140
|
+
* you do, `show-warnings` will not have any affect and you probably don't need to bother using
|
141
|
+
* this class as your base class.
|
142
|
+
*
|
143
|
+
* This method will locate a per-attribute method and call that.
|
144
|
+
* Attribute names are assumed to be in kebab-case and are translated to camelCase to create a method name.
|
145
|
+
* That method is `«attributeInCamelCase»ChangedCallback`, so if your attribute is `hex-code`,
|
146
|
+
* a method named `hexCodeChangedCallback` in invoked. If no such method is defined, a
|
147
|
+
* warning is logged in the console, regardless of the `show-warnings` attribute.
|
148
|
+
*
|
149
|
+
* The method is invoked with `{oldValue,newValue,newValueAsBoolean}` - i.e. an object and not positional parameters. This
|
150
|
+
* means your implementation can omit any parameters it doesn't care about. `newValueAsBoolean` is not part of
|
151
|
+
* the custom element spec, but is provided as an unambiguous way to know if a boolean attribute was set or not. This is
|
152
|
+
* because if the value is set, it is likely to be the empty string, which is considered false by JavaScript. Cool.
|
153
|
+
*
|
154
|
+
* The return value of the method is ignored.
|
155
|
+
*
|
156
|
+
* After your method is called, if there is a method named `update`, it is called with no arguments.
|
157
|
+
*
|
158
|
+
* What this allows you to do is separate how you manage your element's attributes from how your logic
|
159
|
+
* is managed. For complex elements that take a lot of attributes, this can simplify your element's code without straying too far from the spec.
|
160
|
+
*
|
161
|
+
* @example
|
162
|
+
*
|
163
|
+
* // If your element accepts the attribute `warning-message` that will be trimmed of whitespace
|
164
|
+
* // then placed into all `H1` tags inside the element, you can manage that like so:
|
165
|
+
* class MyElement extends BaseCustomElement {
|
166
|
+
* static tagName = "my-element"
|
167
|
+
* static observedAttributes = [
|
168
|
+
* "warning-message",
|
169
|
+
* ]
|
170
|
+
*
|
171
|
+
* // called by attributeChangedCallback when warning-message's value changes
|
172
|
+
* warningMessageChangedCallback({newValue}) {
|
173
|
+
* this.warningMessage = (newValue || "").trim()
|
174
|
+
* }
|
175
|
+
*
|
176
|
+
* // called after attributeChangedCallback calls warningMessageChangedCallback
|
177
|
+
* update() {
|
178
|
+
* this.querySelectorAll("h1").forEach( (e) => e.textContent = this.warningMessage )
|
179
|
+
* }
|
180
|
+
* }
|
181
|
+
*
|
182
|
+
*
|
183
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements}
|
184
|
+
*
|
185
|
+
*/
|
186
|
+
attributeChangedCallback(name,oldValue,newValue) {
|
187
|
+
const callbackName = `${new RichString(name).camelize()}ChangedCallback`
|
188
|
+
if (this[callbackName]) {
|
189
|
+
const newValueAsBoolean = newValue !== null
|
190
|
+
this[callbackName]({oldValue,newValue,newValueAsBoolean})
|
191
|
+
}
|
192
|
+
else if (this.constructor.observedAttributes.indexOf(name) != -1) {
|
193
|
+
console.warn("Observing %s but no method named %s was found to handle it",name,callbackName)
|
194
|
+
}
|
195
|
+
this.__update()
|
196
|
+
}
|
197
|
+
|
198
|
+
/** Overrides the custom element callback to set internal flags allowing you to know if your
|
199
|
+
* element has been disconnected. When an element is disconnected, `update` is not called.
|
200
|
+
*
|
201
|
+
* If you want to add your own logic during disconnection, override {@link onDisconnected}.
|
202
|
+
*
|
203
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements}
|
204
|
+
*/
|
205
|
+
disconnectedCallback() {
|
206
|
+
this.#_disconnectedCallbackCalled = true
|
207
|
+
this.onDisconnected()
|
208
|
+
}
|
209
|
+
|
210
|
+
/** Override this to add logic when `disconnectedCallback` is called by the browser. This will
|
211
|
+
* not be called if you overrode `disconnectedCallback`.
|
212
|
+
*/
|
213
|
+
onDisconnected() {}
|
214
|
+
|
215
|
+
/** Overrides the custom element callback to set internal flags allowing you to know if your
|
216
|
+
* element has been connected. `update` is still called for elements that have not yet connected, however
|
217
|
+
* in practice your element will be connected before any codepath that calls `update` is called.
|
218
|
+
*
|
219
|
+
* To add logic when your element is connected, override {@link onConnected}
|
220
|
+
*
|
221
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements}
|
222
|
+
* @see BaseCustomElement#connectedCallbackCalled
|
223
|
+
*/
|
224
|
+
connectedCallback() {
|
225
|
+
this.#_connectedCallbackCalled = true
|
226
|
+
this.onConnected()
|
227
|
+
this.__update()
|
228
|
+
}
|
229
|
+
|
230
|
+
/** Override this to add logic when `connectedCallback` is called by the browser. This will
|
231
|
+
* not be called if you overrode `connectedCallback`
|
232
|
+
*/
|
233
|
+
onConnected() {}
|
234
|
+
|
235
|
+
/** Returns true if this element is connected and the connected callback has been called.
|
236
|
+
* This is different from `Node#isConnected`, which can return true before `connectedCallback` is called.
|
237
|
+
*/
|
238
|
+
get connectedCallbackCalled() { return !!this.#_connectedCallbackCalled }
|
239
|
+
|
240
|
+
/** Override this to perform whatever logic your element must perform.
|
241
|
+
* Because changes to your element's attributes can happen at any time and in any order,
|
242
|
+
* you will want to consolidate all logic into one method—this one. You will also
|
243
|
+
* want to make sure that this method is idempotent and fault-tolerant. It will be called multiple times.
|
244
|
+
*
|
245
|
+
* It is called by {@link BaseCustomElement#attributeChangedCallback|attributeChangedCallback} and {@link BaseCustomElement#connectedCallback|connectedCallback}, however
|
246
|
+
* it will *not* be called after the elment has been disconnected.
|
247
|
+
*
|
248
|
+
* That means that any event listeners, rendering, content manipulation, or other behavior should happen hear
|
249
|
+
* and it *must* be idempotent. In particular, any event listeners you attach must be done with care. Using
|
250
|
+
* anonymous functions could result in duplicate listeners.
|
251
|
+
*/
|
252
|
+
update() {}
|
253
|
+
|
254
|
+
__update() {
|
255
|
+
if (this.#_disconnectedCallbackCalled) {
|
256
|
+
return
|
257
|
+
}
|
258
|
+
this.update()
|
259
|
+
}
|
260
|
+
}
|
261
|
+
export default BaseCustomElement
|
@@ -0,0 +1,116 @@
|
|
1
|
+
import BaseCustomElement from "./BaseCustomElement"
|
2
|
+
import RichString from "./RichString"
|
3
|
+
import ConfirmationDialog from "./ConfirmationDialog"
|
4
|
+
|
5
|
+
/** Confirms form submissions with the user before allowing the form to be submitted. This can be applied per submit
|
6
|
+
* button and only affects the behavior of `button` or `input type=submit`, and *only* if no other click handler
|
7
|
+
* has been put onto those buttons. Note that this does not affect links (`a` elements).
|
8
|
+
*
|
9
|
+
* This can ask for confirmation with {@link external:Window#confirm} or a
|
10
|
+
* `brut-confirmation-dialog`. What it will use depends on several factors, all of which
|
11
|
+
* are geared toward doing the right thing. Note that setting `show-warnings` will elucidate the reasons
|
12
|
+
* this component does what it does.
|
13
|
+
*
|
14
|
+
* * If `dialog` is set:
|
15
|
+
* - If that id is on a `<brut-confirmation-dialog>` that is used.
|
16
|
+
* - If not, `window.confirm` is used.
|
17
|
+
* * If `dialog` is not set:
|
18
|
+
* - If there is exactly one `<brut-confirmation-dialog>` on the page, this is used.
|
19
|
+
* - If there is more than one, or no `<brut-confirmation-dialog>`s, `window.confirm` is used.
|
20
|
+
*
|
21
|
+
* @see ConfirmationDialog
|
22
|
+
*
|
23
|
+
* @property {string} message - the message to show that asks for confirmation. It should be written such that
|
24
|
+
* "OK" is grammatically correct for confirmation and "Cancel" is for aborting.
|
25
|
+
* @property {string} dialog - optional ID of the `brut-confirmation-dialog` to use instead of `window.confirm`.
|
26
|
+
* If there is no such dialog or the id references the wrong element type,
|
27
|
+
* `window.confirm` will be used. Setting `show-warnings` will generate a warning for this.
|
28
|
+
*
|
29
|
+
* @customElement brut-confirm-submit
|
30
|
+
*/
|
31
|
+
class ConfirmSubmit extends BaseCustomElement {
|
32
|
+
static tagName = "brut-confirm-submit"
|
33
|
+
|
34
|
+
static observedAttributes = [
|
35
|
+
"message",
|
36
|
+
"dialog",
|
37
|
+
"show-warnings",
|
38
|
+
]
|
39
|
+
|
40
|
+
#message = new RichString("")
|
41
|
+
#confirming = false
|
42
|
+
#dialogId = null
|
43
|
+
|
44
|
+
messageChangedCallback({newValue}) {
|
45
|
+
this.#message = new RichString(newValue || "")
|
46
|
+
}
|
47
|
+
|
48
|
+
dialogChangedCallback({newValue}) {
|
49
|
+
this.#dialogId = RichString.fromString(newValue)
|
50
|
+
}
|
51
|
+
|
52
|
+
constructor() {
|
53
|
+
super()
|
54
|
+
this.onClick = (event) => {
|
55
|
+
if (this.#confirming) {
|
56
|
+
this.#confirming = false
|
57
|
+
return
|
58
|
+
}
|
59
|
+
if (this.#message.isBlank()) {
|
60
|
+
this.logger.warn("No message provided, so cannot confirm")
|
61
|
+
return
|
62
|
+
}
|
63
|
+
const dialog = this.#findDialog()
|
64
|
+
if (dialog) {
|
65
|
+
event.preventDefault()
|
66
|
+
dialog.setAttribute("message",this.#message.toString())
|
67
|
+
const buttonLabel = event.target.getAttribute("aria-label") || event.target.textContent
|
68
|
+
dialog.setAttribute("confirm-label",buttonLabel)
|
69
|
+
this.#confirming = true
|
70
|
+
dialog.showModal((confirm) => {
|
71
|
+
if (confirm) {
|
72
|
+
event.target.click()
|
73
|
+
}
|
74
|
+
else {
|
75
|
+
this.#confirming = false
|
76
|
+
}
|
77
|
+
})
|
78
|
+
}
|
79
|
+
else {
|
80
|
+
const result = window.confirm(this.#message)
|
81
|
+
if (!result) {
|
82
|
+
event.preventDefault()
|
83
|
+
}
|
84
|
+
}
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
#findDialog() {
|
89
|
+
if (this.#dialogId) {
|
90
|
+
const dialog = document.getElementById(this.#dialogId)
|
91
|
+
if (dialog) {
|
92
|
+
if (dialog.tagName.toLowerCase() != ConfirmationDialog.tagName) {
|
93
|
+
throw `${this.#dialogId} is the id of a '${dialog.tagName}', not '${ConfirmationDialog.tagName}'`
|
94
|
+
}
|
95
|
+
return dialog
|
96
|
+
}
|
97
|
+
this.logger.warn(`No dialog with id ${this.#dialogId} - using window.confirm as a fallback`)
|
98
|
+
return null
|
99
|
+
}
|
100
|
+
const dialogs = document.querySelectorAll(ConfirmationDialog.tagName)
|
101
|
+
if (dialogs.length == 1) {
|
102
|
+
return dialogs[0]
|
103
|
+
}
|
104
|
+
if (dialogs.length == 0) {
|
105
|
+
this.logger.warn(`No '${ConfirmationDialog.tagName}' found in document - using window.confirm as a fallback`)
|
106
|
+
return null
|
107
|
+
}
|
108
|
+
throw `Found ${dialogs.length} '${ConfirmationDialog.tagName}' elements. Not sure which to use. Remove all but one or specify the 'dialog' attribute on this element to specify which one to use`
|
109
|
+
}
|
110
|
+
|
111
|
+
update() {
|
112
|
+
this.querySelectorAll("button").forEach( (button) => button.addEventListener("click", this.onClick) )
|
113
|
+
this.querySelectorAll("input[type=submit]").forEach( (button) => button.addEventListener("click", this.onClick) )
|
114
|
+
}
|
115
|
+
}
|
116
|
+
export default ConfirmSubmit
|
@@ -0,0 +1,143 @@
|
|
1
|
+
import BaseCustomElement from "./BaseCustomElement"
|
2
|
+
import RichString from "./RichString"
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Enhances a `DIALOG` element to allow it to be used for confirmation. Mostly useful
|
6
|
+
* with {@link Confirm}.
|
7
|
+
*
|
8
|
+
* This element must:
|
9
|
+
*
|
10
|
+
* * Wrap a `<DIALOG>` element
|
11
|
+
* * Contain an `<H1>` where the message will go
|
12
|
+
* * Have a `<BUTTON>` with value `ok` that will be considered the confirmation button.
|
13
|
+
* * Have a `<BUTTON>` with value `cancel` that will be considered the denial button.
|
14
|
+
*
|
15
|
+
* Omitting these will cause this element to not work properly. Set `show-warnings` to see
|
16
|
+
* warnings on this.
|
17
|
+
*
|
18
|
+
* @property {string} message - the message to use to ask for confirmation
|
19
|
+
* @property {string} confirm-label - the label to use for the "OK" or "Confirm" button
|
20
|
+
*
|
21
|
+
* @example <caption>Minimal Example</caption>
|
22
|
+
* <brut-confirmation-dialog message="This cannot be undone" confirm-label="DOIT">
|
23
|
+
* <dialog>
|
24
|
+
* <h1></h1>
|
25
|
+
* <button value="ok"></button>
|
26
|
+
* <button value="cancel">Nevermind</button>
|
27
|
+
* </dialog>
|
28
|
+
* </brut-confirmation-dialog>
|
29
|
+
*
|
30
|
+
* @customElement brut-confirmation-dialog
|
31
|
+
*/
|
32
|
+
class ConfirmationDialog extends BaseCustomElement {
|
33
|
+
static tagName = "brut-confirmation-dialog"
|
34
|
+
static observedAttributes = [
|
35
|
+
"message",
|
36
|
+
"confirm-label",
|
37
|
+
"show-warnings"
|
38
|
+
]
|
39
|
+
|
40
|
+
#onClose = () => {}
|
41
|
+
#message = new RichString("")
|
42
|
+
#confirmLabel = new RichString("OK")
|
43
|
+
|
44
|
+
constructor() {
|
45
|
+
super()
|
46
|
+
this.okListener = (event) => {
|
47
|
+
this.#closeDialog()
|
48
|
+
this.#onClose(true)
|
49
|
+
}
|
50
|
+
this.cancelListener = (event) => {
|
51
|
+
this.#closeDialog()
|
52
|
+
this.#onClose(false)
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
messageChangedCallback({newValue}) {
|
57
|
+
this.#message = RichString.fromString(newValue)
|
58
|
+
}
|
59
|
+
|
60
|
+
confirmLabelChangedCallback({newValue}) {
|
61
|
+
this.#confirmLabel = RichString.fromString(newValue)
|
62
|
+
}
|
63
|
+
|
64
|
+
/**
|
65
|
+
* Call this to show the dialog. When the dialog is closed, `onClose` is called with the result.
|
66
|
+
*
|
67
|
+
* @param {function} onClose - a function called with either `true` or `false`, if the dialog was confirmed or
|
68
|
+
* denied, respectively.
|
69
|
+
*
|
70
|
+
* @example
|
71
|
+
* dialog.showModal( (confirmed) => {
|
72
|
+
* if (confirmed) {
|
73
|
+
* form.submit()
|
74
|
+
* }
|
75
|
+
* else {
|
76
|
+
* // do nothing
|
77
|
+
* }
|
78
|
+
* })
|
79
|
+
*/
|
80
|
+
showModal(onClose) {
|
81
|
+
const dialog = this.#dialog
|
82
|
+
if (dialog) {
|
83
|
+
this.#onClose = onClose || (() => {})
|
84
|
+
dialog.showModal()
|
85
|
+
}
|
86
|
+
else {
|
87
|
+
this.logger.warn("No <dialog> found to show")
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
get #dialog() {
|
92
|
+
return this.querySelector("dialog")
|
93
|
+
}
|
94
|
+
|
95
|
+
#closeDialog() {
|
96
|
+
const dialog = this.#dialog
|
97
|
+
if (dialog) {
|
98
|
+
dialog.close()
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
update() {
|
103
|
+
const dialog = this.#dialog
|
104
|
+
if (!dialog) {
|
105
|
+
this.logger.warn("Could not find a <dialog> - this custom element won't do anything")
|
106
|
+
return
|
107
|
+
}
|
108
|
+
this.#setMessage(dialog)
|
109
|
+
this.#setupButtons()
|
110
|
+
}
|
111
|
+
|
112
|
+
#setMessage(dialog) {
|
113
|
+
const h1 = dialog.querySelector("h1")
|
114
|
+
if (h1) {
|
115
|
+
if (this.#message.isBlank()) {
|
116
|
+
h1.textContent = null
|
117
|
+
}
|
118
|
+
else {
|
119
|
+
h1.textContent = this.#message.toString()
|
120
|
+
}
|
121
|
+
}
|
122
|
+
else {
|
123
|
+
this.logger.warn("Dialog had no <h1>, so nowhere to put the message")
|
124
|
+
}
|
125
|
+
}
|
126
|
+
|
127
|
+
#setupButtons() {
|
128
|
+
const okButton = this.querySelector("button[value='ok']")
|
129
|
+
const cancelButton = this.querySelector("button[value='cancel']")
|
130
|
+
|
131
|
+
if (!okButton || !cancelButton) {
|
132
|
+
if (!okButton) { this.logger.warn("no <button value='ok'> which is required for this dialog to work") }
|
133
|
+
if (!cancelButton) { this.logger.warn("no <button value='cancel'> which is required for this dialog to work") }
|
134
|
+
return
|
135
|
+
}
|
136
|
+
|
137
|
+
okButton.textContent = this.#confirmLabel
|
138
|
+
|
139
|
+
okButton.addEventListener("click" , this.okListener)
|
140
|
+
cancelButton.addEventListener("click" , this.cancelListener)
|
141
|
+
}
|
142
|
+
}
|
143
|
+
export default ConfirmationDialog
|
@@ -0,0 +1,125 @@
|
|
1
|
+
import BaseCustomElement from "./BaseCustomElement"
|
2
|
+
import RichString from "./RichString"
|
3
|
+
import I18nTranslation from "./I18nTranslation"
|
4
|
+
|
5
|
+
/** Like {@link Message} but specific to constraint violations of input fields. This accepts the name
|
6
|
+
* of an input field via `input-name`, which can be used to locate the field's localized name.
|
7
|
+
*
|
8
|
+
* Here is how the field's name is determined:
|
9
|
+
*
|
10
|
+
* 1. It will look for a `<brut-i18n-translation>` element with the `key` `cv.fe.fieldNames.«input-name»`.
|
11
|
+
* 2. If that's not found, it will attempt to use "this field" by locating a `<brut-i18n-translation>` element with the `key`
|
12
|
+
* `cv.this_field` (the underscore being what is used on Brut's server side).
|
13
|
+
* 3. If that is not found, it will use the literaly string "this field" and emit a console warning.
|
14
|
+
*
|
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
|
+
* the element will not render any text.
|
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-side if true, this indicates the element contains constraint violation messages
|
19
|
+
* from the server. Currently doesn't affect this element's behavior, however
|
20
|
+
* AjaxSubmit will use it to locate where it should insert server-side errors.
|
21
|
+
*
|
22
|
+
* @see I18nTranslation
|
23
|
+
* @see ConstraintViolationMessages
|
24
|
+
* @see Message
|
25
|
+
*
|
26
|
+
* @customElement brut-cv
|
27
|
+
*/
|
28
|
+
class ConstraintViolationMessage extends BaseCustomElement {
|
29
|
+
static tagName = "brut-cv"
|
30
|
+
|
31
|
+
static observedAttributes = [
|
32
|
+
"show-warnings",
|
33
|
+
"key",
|
34
|
+
"input-name",
|
35
|
+
"server-side",
|
36
|
+
]
|
37
|
+
|
38
|
+
static createElement(document,attributes) {
|
39
|
+
const element = document.createElement(ConstraintViolationMessage.tagName)
|
40
|
+
element.setAttribute("key",this.i18nKey("fe", attributes.key))
|
41
|
+
element.setAttribute("input-name",attributes["input-name"])
|
42
|
+
if (Object.hasOwn(attributes,"show-warnings")) {
|
43
|
+
element.setAttribute("show-warnings",attributes["show-warnings"])
|
44
|
+
}
|
45
|
+
return element
|
46
|
+
}
|
47
|
+
|
48
|
+
static markServerSide(element) {
|
49
|
+
if (element.tagName.toLowerCase() == this.tagName) {
|
50
|
+
element.setAttribute("server-side", true)
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
static clientSideSelector() {
|
55
|
+
return `${this.tagName}:not([server-side])`
|
56
|
+
}
|
57
|
+
|
58
|
+
static serverSideSelector() {
|
59
|
+
return `${this.tagName}[server-side]`
|
60
|
+
}
|
61
|
+
|
62
|
+
/** Returns the I18N key used for front-end constraint violations. This is useful
|
63
|
+
* if you need to construct a key and want to follow Brut's conventions on how they
|
64
|
+
* are managed.
|
65
|
+
*
|
66
|
+
* @param {...String} keyPath - parts of the path of the key after the namespace that Brut manages.
|
67
|
+
*/
|
68
|
+
static i18nKey(...keyPath) {
|
69
|
+
const path = [ "cv" ]
|
70
|
+
return path.concat(keyPath).join(".")
|
71
|
+
}
|
72
|
+
|
73
|
+
#key = null
|
74
|
+
#inputNameKey = null
|
75
|
+
#thisFieldKey = this.#i18nKey("this_field")
|
76
|
+
|
77
|
+
keyChangedCallback({newValue}) {
|
78
|
+
this.#key = newValue
|
79
|
+
}
|
80
|
+
|
81
|
+
inputNameChangedCallback({newValue}) {
|
82
|
+
this.#inputNameKey = this.#i18nKey("fe", "fieldNames", newValue)
|
83
|
+
}
|
84
|
+
|
85
|
+
serverSideChangedCallback({newValueAsBoolean}) {
|
86
|
+
// attribute listed for documentation purposes only
|
87
|
+
}
|
88
|
+
|
89
|
+
update() {
|
90
|
+
if (!this.#key) {
|
91
|
+
this.logger.info("No key attribute, so can't do anything")
|
92
|
+
return
|
93
|
+
}
|
94
|
+
|
95
|
+
const selector = `${I18nTranslation.tagName}[key='${this.#key}']`
|
96
|
+
const translation = document.querySelector(selector)
|
97
|
+
if (!translation) {
|
98
|
+
this.logger.info("Could not find translation based on selector '%s'",selector)
|
99
|
+
return
|
100
|
+
}
|
101
|
+
|
102
|
+
const fieldNameSelector = `${I18nTranslation.tagName}[key='${this.#inputNameKey}']`
|
103
|
+
const thisFieldSelector = `${I18nTranslation.tagName}[key='${this.#thisFieldKey}']`
|
104
|
+
|
105
|
+
let fieldNameTranslation = document.querySelector(fieldNameSelector)
|
106
|
+
if (!fieldNameTranslation) {
|
107
|
+
this.logger.info("Could not find translation for input/field name based on selector '%s'. Will try 'this field' fallback",fieldNameSelector)
|
108
|
+
fieldNameTranslation = document.querySelector(thisFieldSelector)
|
109
|
+
if (!fieldNameTranslation) {
|
110
|
+
this.logger.info("Could not find translation for 'this field' fallback key, based on selector '%s'",thisFieldSelector)
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
const fieldName = fieldNameTranslation ? fieldNameTranslation.translation() : "this field"
|
115
|
+
this.textContent = RichString.fromString(translation.translation({ field: fieldName })).capitalize().toString()
|
116
|
+
}
|
117
|
+
|
118
|
+
/** Helper that calls the static version */
|
119
|
+
#i18nKey(...keyPath) {
|
120
|
+
return this.constructor.i18nKey(...keyPath)
|
121
|
+
}
|
122
|
+
|
123
|
+
|
124
|
+
}
|
125
|
+
export default ConstraintViolationMessage
|