From 3c80c6ae83d46eb7413eee68efa6cae048cfcd28 Mon Sep 17 00:00:00 2001 From: effulgentsia Date: Sun, 13 Oct 2019 13:42:58 -0700 Subject: [PATCH] =?UTF-8?q?Issue=20#3079738=20by=20lauriii,=20saschaeggi,?= =?UTF-8?q?=20webchick,=20xjm,=20andrewmacpherson,=20shimpy,=20effulgentsi?= =?UTF-8?q?a,=20Wim=20Leers,=20DyanneNova,=20svettes,=20rainbreaw,=20fhaeb?= =?UTF-8?q?erle,=20ckrina,=20AaronMcHale,=20justafish,=20catch,=20charliew?= =?UTF-8?q?eb82,=20AntoineH,=20lot007,=20pzajacz,=20kostyashupenko,=20jaso?= =?UTF-8?q?nbarrie,=20antonellasevero,=20finnsky,=20worldlinemine,=20bnjmn?= =?UTF-8?q?m,=20RobLoach,=20Dennis=20Cohn,=20huzooka,=20Archita=20Arora,?= =?UTF-8?q?=20joachim,=20jrockowitz,=20benjifisher,=20shaal,=20G=C3=A1bor?= =?UTF-8?q?=20Hojtsy,=20quiron,=20L2G2,=20ccasals,=20hampercm,=20if-jds,?= =?UTF-8?q?=20abhisekmazumdar,=20Kami=20Amiga,=20pivica,=20zrpnr,=20Bright?= =?UTF-8?q?Bold,=20imalabya,=20jhedstrom,=20Neslee=20Canil=20Pinto,=20mali?= =?UTF-8?q?knaik,=20junaidmasoodi,=20Maithri=20Shetty,=20pranav73,=20mandc?= =?UTF-8?q?lu,=20modulist,=20nod=5F,=20philosurfer,=20phenaproxima,=20mher?= =?UTF-8?q?chel,=20mlncn,=20rafuel92,=20leymannx,=20kiboman,=20Swapnil=5FK?= =?UTF-8?q?otwal,=20anevins,=20evankay,=20rfmarcelino,=20thamas,=20brianpe?= =?UTF-8?q?rry,=20idebr,=20joelpittet,=20boulaffasae,=20alexpott,=20volker?= =?UTF-8?q?k,=20DuneBL,=20Eli-T,=20Mahenkvyas22:=20Add=20Claro=20administr?= =?UTF-8?q?ation=20theme=20to=20core?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 1786bc19c8270769e04c6ea842fc7f294f7b1e3e) --- core/.stylelintignore | 2 + core/composer.json | 1 + core/modules/shortcut/shortcut.install | 18 +- core/modules/shortcut/shortcut.module | 12 +- .../Theme/ClaroBlockFilterTest.php | 36 + .../Theme/ClaroEntityDisplayTest.php | 116 + .../Theme/ClaroMenuUiJavascriptTest.php | 41 + .../FunctionalTests/Theme/ClaroTest.php | 75 + core/themes/claro/claro.info.yml | 175 ++ core/themes/claro/claro.libraries.yml | 249 +++ core/themes/claro/claro.theme | 1192 +++++++++++ .../block.block.claro_breadcrumbs.yml | 19 + .../optional/block.block.claro_content.yml | 19 + .../optional/block.block.claro_help.yml | 19 + .../block.block.claro_local_actions.yml | 17 + .../optional/block.block.claro_messages.yml | 19 + .../optional/block.block.claro_page_title.yml | 17 + .../block.block.claro_primary_local_tasks.yml | 19 + ...lock.block.claro_secondary_local_tasks.yml | 19 + .../claro/config/schema/claro.schema.yml | 5 + core/themes/claro/css/src/base/elements.css | 316 +++ .../claro/css/src/base/elements.pcss.css | 213 ++ core/themes/claro/css/src/base/print.css | 89 + core/themes/claro/css/src/base/print.pcss.css | 82 + core/themes/claro/css/src/base/typography.css | 42 + .../claro/css/src/base/typography.pcss.css | 36 + core/themes/claro/css/src/base/variables.css | 49 + .../claro/css/src/base/variables.pcss.css | 180 ++ .../claro/css/src/components/accordion.css | 103 + .../css/src/components/accordion.pcss.css | 46 + .../claro/css/src/components/action-link.css | 498 +++++ .../css/src/components/action-link.pcss.css | 386 ++++ .../src/components/ajax-progress.module.css | 238 +++ .../components/ajax-progress.module.pcss.css | 127 ++ .../autocomplete-loading.module.css | 148 ++ .../autocomplete-loading.module.pcss.css | 86 + .../claro/css/src/components/breadcrumb.css | 90 + .../css/src/components/breadcrumb.pcss.css | 41 + .../claro/css/src/components/button.css | 247 +++ .../claro/css/src/components/button.pcss.css | 170 ++ core/themes/claro/css/src/components/card.css | 290 +++ .../claro/css/src/components/card.pcss.css | 161 ++ .../css/src/components/container-inline.css | 24 + .../components/container-inline.module.css | 27 + .../container-inline.module.pcss.css | 20 + .../src/components/container-inline.pcss.css | 15 + .../css/src/components/content-header.css | 61 + .../src/components/content-header.pcss.css | 12 + .../claro/css/src/components/details.css | 705 ++++++ .../claro/css/src/components/details.pcss.css | 568 +++++ .../claro/css/src/components/dialog.css | 156 ++ .../claro/css/src/components/dialog.pcss.css | 127 ++ .../claro/css/src/components/divider.css | 59 + .../claro/css/src/components/divider.pcss.css | 10 + .../claro/css/src/components/dropbutton.css | 539 +++++ .../css/src/components/dropbutton.pcss.css | 406 ++++ .../claro/css/src/components/entity-meta.css | 95 + .../css/src/components/entity-meta.pcss.css | 50 + .../claro/css/src/components/fieldset.css | 205 ++ .../css/src/components/fieldset.pcss.css | 142 ++ core/themes/claro/css/src/components/file.css | 72 + .../claro/css/src/components/file.pcss.css | 22 + .../components/form--checkbox-radio--ie.css | 210 ++ .../form--checkbox-radio--ie.pcss.css | 139 ++ .../src/components/form--checkbox-radio.css | 277 +++ .../components/form--checkbox-radio.pcss.css | 177 ++ .../src/components/form--field-multiple.css | 65 + .../components/form--field-multiple.pcss.css | 16 + .../css/src/components/form--managed-file.css | 387 ++++ .../components/form--managed-file.pcss.css | 257 +++ .../src/components/form--password-confirm.css | 279 +++ .../form--password-confirm.pcss.css | 198 ++ .../claro/css/src/components/form--select.css | 109 + .../css/src/components/form--select.pcss.css | 44 + .../claro/css/src/components/form--text.css | 228 ++ .../css/src/components/form--text.pcss.css | 142 ++ core/themes/claro/css/src/components/form.css | 269 +++ .../claro/css/src/components/form.pcss.css | 165 ++ core/themes/claro/css/src/components/help.css | 15 + .../claro/css/src/components/help.pcss.css | 8 + .../css/src/components/image-preview.css | 110 + .../css/src/components/image-preview.pcss.css | 53 + .../css/src/components/jquery.ui/theme.css | 746 +++++++ .../src/components/jquery.ui/theme.pcss.css | 470 ++++ .../themes/claro/css/src/components/media.css | 9 + .../claro/css/src/components/media.pcss.css | 3 + .../css/src/components/menus-and-lists.css | 47 + .../src/components/menus-and-lists.pcss.css | 41 + .../claro/css/src/components/messages.css | 203 ++ .../css/src/components/messages.pcss.css | 149 ++ .../claro/css/src/components/modules-page.css | 39 + .../css/src/components/modules-page.pcss.css | 33 + core/themes/claro/css/src/components/node.css | 9 + .../claro/css/src/components/node.pcss.css | 3 + .../claro/css/src/components/page-title.css | 81 + .../css/src/components/page-title.pcss.css | 31 + .../themes/claro/css/src/components/pager.css | 243 +++ .../claro/css/src/components/pager.pcss.css | 155 ++ .../claro/css/src/components/progress.css | 158 ++ .../css/src/components/progress.pcss.css | 86 + .../claro/css/src/components/quickedit.css | 85 + .../css/src/components/quickedit.pcss.css | 63 + .../src/components/search-admin-settings.css | 23 + .../components/search-admin-settings.pcss.css | 14 + .../claro/css/src/components/shortcut.css | 148 ++ .../css/src/components/shortcut.pcss.css | 73 + .../claro/css/src/components/skip-link.css | 36 + .../css/src/components/skip-link.pcss.css | 25 + .../components/system-admin--admin-list.css | 132 ++ .../system-admin--admin-list.pcss.css | 71 + .../src/components/system-admin--links.css | 40 + .../components/system-admin--links.pcss.css | 29 + .../src/components/system-admin--modules.css | 164 ++ .../components/system-admin--modules.pcss.css | 125 ++ .../src/components/system-admin--panel.css | 75 + .../components/system-admin--panel.pcss.css | 24 + .../system-admin--status-report.css | 66 + .../system-admin--status-report.pcss.css | 51 + .../src/components/system-status-counter.css | 105 + .../components/system-status-counter.pcss.css | 87 + .../system-status-report-counters.css | 43 + .../system-status-report-counters.pcss.css | 26 + .../system-status-report-general-info.css | 203 ++ ...system-status-report-general-info.pcss.css | 164 ++ .../src/components/system-status-report.css | 187 ++ .../components/system-status-report.pcss.css | 154 ++ .../table--file-multiple-widget.css | 165 ++ .../table--file-multiple-widget.pcss.css | 110 + .../claro/css/src/components/tabledrag.css | 428 ++++ .../css/src/components/tabledrag.pcss.css | 326 +++ .../claro/css/src/components/tables.css | 339 +++ .../claro/css/src/components/tables.pcss.css | 244 +++ .../claro/css/src/components/tableselect.css | 72 + .../css/src/components/tableselect.pcss.css | 22 + .../src/components/tablesort-indicator.css | 60 + .../components/tablesort-indicator.pcss.css | 46 + core/themes/claro/css/src/components/tabs.css | 439 ++++ .../claro/css/src/components/tabs.pcss.css | 269 +++ .../claro/css/src/components/tour.theme.css | 127 ++ .../css/src/components/tour.theme.pcss.css | 103 + .../css/src/components/vertical-tabs.css | 429 ++++ .../css/src/components/vertical-tabs.pcss.css | 348 +++ .../css/src/components/views-exposed-form.css | 118 ++ .../components/views-exposed-form.pcss.css | 49 + .../claro/css/src/components/views-ui.css | 509 +++++ .../css/src/components/views-ui.pcss.css | 456 ++++ .../claro/css/src/layout/breadcrumb.css | 59 + .../claro/css/src/layout/breadcrumb.pcss.css | 10 + .../themes/claro/css/src/layout/card-list.css | 242 +++ .../claro/css/src/layout/card-list.pcss.css | 143 ++ .../claro/css/src/layout/image-widget.css | 15 + .../css/src/layout/image-widget.pcss.css | 8 + core/themes/claro/css/src/layout/layout.css | 19 + .../claro/css/src/layout/layout.pcss.css | 14 + .../claro/css/src/layout/local-actions.css | 99 + .../css/src/layout/local-actions.pcss.css | 45 + core/themes/claro/css/src/layout/node-add.css | 81 + .../claro/css/src/layout/node-add.pcss.css | 31 + .../css/src/layout/system-admin--layout.css | 91 + .../src/layout/system-admin--layout.pcss.css | 79 + .../claro/css/src/theme/ckeditor-dialog.css | 375 ++++ .../css/src/theme/ckeditor-dialog.pcss.css | 256 +++ .../claro/css/src/theme/ckeditor-editor.css | 177 ++ .../css/src/theme/ckeditor-editor.pcss.css | 114 + .../claro/css/src/theme/ckeditor-frame.css | 61 + .../css/src/theme/ckeditor-frame.pcss.css | 10 + core/themes/claro/css/src/theme/colors.css | 21 + .../claro/css/src/theme/colors.pcss.css | 15 + .../claro/css/src/theme/field-ui.admin.css | 138 ++ .../css/src/theme/field-ui.admin.pcss.css | 67 + .../claro/css/src/theme/filter.theme.css | 142 ++ .../claro/css/src/theme/filter.theme.pcss.css | 83 + .../claro/css/src/theme/install-page.css | 68 + .../claro/css/src/theme/install-page.pcss.css | 62 + .../claro/css/src/theme/maintenance-page.css | 222 ++ .../css/src/theme/maintenance-page.pcss.css | 204 ++ .../css/src/theme/views_ui.admin.theme.css | 1004 +++++++++ .../src/theme/views_ui.admin.theme.pcss.css | 809 +++++++ .../claro/images/core/0074bd/chevron-left.svg | 1 + .../images/core/0074bd/chevron-right.svg | 1 + .../claro/images/core/333333/caret-down.svg | 1 + .../themes/claro/images/core/73b355/check.svg | 1 + core/themes/claro/images/core/787878/cog.svg | 1 + core/themes/claro/images/core/787878/key.svg | 1 + .../images/core/787878/questionmark-disc.svg | 1 + core/themes/claro/images/core/README.md | 4 + .../themes/claro/images/core/cccccc/clock.svg | 3 + .../claro/images/core/cccccc/d8-logo.svg | 1 + .../claro/images/core/cccccc/database.svg | 1 + .../claro/images/core/cccccc/php-logo.svg | 1 + .../claro/images/core/cccccc/server.svg | 3 + .../claro/images/core/e29700/warning.svg | 1 + .../themes/claro/images/core/e32700/error.svg | 1 + .../claro/images/core/ee0000/required.svg | 1 + core/themes/claro/images/core/ffffff/ex.svg | 1 + .../images/core/stable/views_ui/sprites.png | 8 + core/themes/claro/images/noise-low.png | 29 + .../claro/images/shortcut/favstar-rtl.svg | 1 + core/themes/claro/images/shortcut/favstar.svg | 1 + core/themes/claro/images/spinner-ltr.gif | 10 + core/themes/claro/images/spinner-rtl.gif | 12 + core/themes/claro/images/src/arrow-down.svg | 1 + core/themes/claro/images/src/arrow-left.svg | 1 + core/themes/claro/images/src/arrow-right.svg | 1 + core/themes/claro/images/src/arrow-up.svg | 1 + core/themes/claro/images/src/checkmark.svg | 1 + core/themes/claro/images/src/cog.svg | 1 + core/themes/claro/images/src/ex.svg | 1 + .../claro/images/src/hamburger-menu.svg | 1 + core/themes/claro/images/src/hide.svg | 1 + .../themes/claro/images/src/magnifier-rtl.svg | 1 + core/themes/claro/images/src/magnifier.svg | 1 + .../claro/images/src/message--error.svg | 1 + .../claro/images/src/message--status.svg | 3 + .../claro/images/src/message--warning.svg | 1 + core/themes/claro/images/src/plus.svg | 1 + core/themes/claro/images/src/select.svg | 1 + core/themes/claro/images/src/show.svg | 1 + core/themes/claro/images/src/sort--asc.svg | 1 + core/themes/claro/images/src/sort--desc.svg | 1 + .../claro/images/src/sort--inactive--ltr.svg | 1 + .../claro/images/src/sort--inactive--rtl.svg | 1 + .../claro/images/src/tabledrag-handle.svg | 1 + .../claro/images/src/tabledrag-tree.svg | 1 + core/themes/claro/images/src/trash.svg | 1 + .../claro/images/ui-icons-222222-256x240.png | 28 + .../claro/images/ui-icons-454545-256x240.png | 28 + .../claro/images/ui-icons-800000-256x240.png | 28 + .../claro/images/ui-icons-888888-256x240.png | 28 + .../claro/images/ui-icons-ffffff-256x240.png | 28 + core/themes/claro/js/ajax.es6.js | 45 + core/themes/claro/js/ajax.js | 23 + core/themes/claro/js/autocomplete.es6.js | 34 + core/themes/claro/js/autocomplete.js | 29 + core/themes/claro/js/checkbox.es6.js | 15 + core/themes/claro/js/checkbox.js | 12 + core/themes/claro/js/details.es6.js | 58 + core/themes/claro/js/details.js | 34 + core/themes/claro/js/dropbutton.es6.js | 23 + core/themes/claro/js/dropbutton.js | 12 + core/themes/claro/js/messages.es6.js | 74 + core/themes/claro/js/messages.js | 39 + core/themes/claro/js/mobile.install.es6.js | 29 + core/themes/claro/js/mobile.install.js | 36 + core/themes/claro/js/nav-tabs.es6.js | 88 + core/themes/claro/js/nav-tabs.js | 78 + .../themes/claro/js/responsive-details.es6.js | 52 + core/themes/claro/js/responsive-details.js | 41 + core/themes/claro/js/tabledrag.es6.js | 1883 +++++++++++++++++ core/themes/claro/js/tabledrag.js | 1001 +++++++++ core/themes/claro/js/user.es6.js | 344 +++ core/themes/claro/js/user.js | 181 ++ core/themes/claro/js/vertical-tabs.es6.js | 470 ++++ core/themes/claro/js/vertical-tabs.js | 200 ++ core/themes/claro/logo.svg | 1 + core/themes/claro/screenshot.png | 234 ++ core/themes/claro/src/ClaroPreRender.php | 193 ++ .../admin/admin-block-content.html.twig | 34 + .../templates/admin/admin-page.html.twig | 25 + ..._translation_manage_form_element.html.twig | 21 + .../templates/admin/indentation.html.twig | 36 + .../admin/tablesort-indicator.html.twig | 28 + .../templates/admin/update-version.html.twig | 34 + .../views-ui-views-listing-table.html.twig | 50 + .../block--local-actions-block.html.twig | 14 + .../block-content-add-list.html.twig | 20 + .../block/block--local-tasks-block.html.twig | 12 + .../claro/templates/breadcrumb.html.twig | 25 + .../content-edit/file-managed-file.html.twig | 47 + .../file-widget-multiple.html.twig | 20 + .../content-edit/image-widget.html.twig | 59 + .../claro/templates/datetime-form.html.twig | 15 + .../templates/datetime-wrapper.html.twig | 34 + core/themes/claro/templates/details.html.twig | 91 + .../claro/templates/entity-add-list.html.twig | 49 + .../claro/templates/field/file-link.html.twig | 18 + .../themes/claro/templates/fieldset.html.twig | 81 + .../filter/filter-guidelines.html.twig | 29 + .../templates/filter/filter-tips.html.twig | 66 + .../templates/form-element-label.html.twig | 12 + .../claro/templates/form-element.html.twig | 64 + .../form/field-multiple-value-form.html.twig | 52 + .../claro/templates/form/input.html.twig | 24 + .../claro/templates/install-page.html.twig | 51 + .../templates/maintenance-page.html.twig | 40 + .../templates/menu-local-tasks.html.twig | 27 + .../templates/misc/status-messages.html.twig | 72 + .../details--vertical-tabs.html.twig | 79 + .../menu-local-task--views-ui.html.twig | 19 + .../navigation/menu-local-task.html.twig | 25 + .../claro/templates/node-add-list.html.twig | 25 + .../claro/templates/node-edit-form.html.twig | 32 + .../off-canvas-page-wrapper.html.twig | 26 + core/themes/claro/templates/page.html.twig | 62 + core/themes/claro/templates/pager.html.twig | 127 ++ .../templates/region--breadcrumb.html.twig | 23 + .../templates/status-report-counter.html.twig | 26 + .../status-report-general-info.html.twig | 99 + .../templates/status-report-grouped.html.twig | 55 + .../templates/status-report-page.html.twig | 28 + .../templates/system-themes-page.html.twig | 113 + .../templates/text-format-wrapper.html.twig | 41 + .../templates/views-exposed-form.html.twig | 19 + .../views/views-mini-pager.html.twig | 54 + 304 files changed, 33787 insertions(+), 9 deletions(-) create mode 100644 core/.stylelintignore create mode 100644 core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php create mode 100644 core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php create mode 100644 core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php create mode 100644 core/tests/Drupal/FunctionalTests/Theme/ClaroTest.php create mode 100644 core/themes/claro/claro.info.yml create mode 100644 core/themes/claro/claro.libraries.yml create mode 100644 core/themes/claro/claro.theme create mode 100644 core/themes/claro/config/optional/block.block.claro_breadcrumbs.yml create mode 100644 core/themes/claro/config/optional/block.block.claro_content.yml create mode 100644 core/themes/claro/config/optional/block.block.claro_help.yml create mode 100644 core/themes/claro/config/optional/block.block.claro_local_actions.yml create mode 100644 core/themes/claro/config/optional/block.block.claro_messages.yml create mode 100644 core/themes/claro/config/optional/block.block.claro_page_title.yml create mode 100644 core/themes/claro/config/optional/block.block.claro_primary_local_tasks.yml create mode 100644 core/themes/claro/config/optional/block.block.claro_secondary_local_tasks.yml create mode 100644 core/themes/claro/config/schema/claro.schema.yml create mode 100644 core/themes/claro/css/src/base/elements.css create mode 100644 core/themes/claro/css/src/base/elements.pcss.css create mode 100644 core/themes/claro/css/src/base/print.css create mode 100644 core/themes/claro/css/src/base/print.pcss.css create mode 100644 core/themes/claro/css/src/base/typography.css create mode 100644 core/themes/claro/css/src/base/typography.pcss.css create mode 100644 core/themes/claro/css/src/base/variables.css create mode 100644 core/themes/claro/css/src/base/variables.pcss.css create mode 100644 core/themes/claro/css/src/components/accordion.css create mode 100644 core/themes/claro/css/src/components/accordion.pcss.css create mode 100644 core/themes/claro/css/src/components/action-link.css create mode 100644 core/themes/claro/css/src/components/action-link.pcss.css create mode 100644 core/themes/claro/css/src/components/ajax-progress.module.css create mode 100644 core/themes/claro/css/src/components/ajax-progress.module.pcss.css create mode 100644 core/themes/claro/css/src/components/autocomplete-loading.module.css create mode 100644 core/themes/claro/css/src/components/autocomplete-loading.module.pcss.css create mode 100644 core/themes/claro/css/src/components/breadcrumb.css create mode 100644 core/themes/claro/css/src/components/breadcrumb.pcss.css create mode 100644 core/themes/claro/css/src/components/button.css create mode 100644 core/themes/claro/css/src/components/button.pcss.css create mode 100644 core/themes/claro/css/src/components/card.css create mode 100644 core/themes/claro/css/src/components/card.pcss.css create mode 100644 core/themes/claro/css/src/components/container-inline.css create mode 100644 core/themes/claro/css/src/components/container-inline.module.css create mode 100644 core/themes/claro/css/src/components/container-inline.module.pcss.css create mode 100644 core/themes/claro/css/src/components/container-inline.pcss.css create mode 100644 core/themes/claro/css/src/components/content-header.css create mode 100644 core/themes/claro/css/src/components/content-header.pcss.css create mode 100644 core/themes/claro/css/src/components/details.css create mode 100644 core/themes/claro/css/src/components/details.pcss.css create mode 100644 core/themes/claro/css/src/components/dialog.css create mode 100644 core/themes/claro/css/src/components/dialog.pcss.css create mode 100644 core/themes/claro/css/src/components/divider.css create mode 100644 core/themes/claro/css/src/components/divider.pcss.css create mode 100644 core/themes/claro/css/src/components/dropbutton.css create mode 100644 core/themes/claro/css/src/components/dropbutton.pcss.css create mode 100644 core/themes/claro/css/src/components/entity-meta.css create mode 100644 core/themes/claro/css/src/components/entity-meta.pcss.css create mode 100644 core/themes/claro/css/src/components/fieldset.css create mode 100644 core/themes/claro/css/src/components/fieldset.pcss.css create mode 100644 core/themes/claro/css/src/components/file.css create mode 100644 core/themes/claro/css/src/components/file.pcss.css create mode 100644 core/themes/claro/css/src/components/form--checkbox-radio--ie.css create mode 100644 core/themes/claro/css/src/components/form--checkbox-radio--ie.pcss.css create mode 100644 core/themes/claro/css/src/components/form--checkbox-radio.css create mode 100644 core/themes/claro/css/src/components/form--checkbox-radio.pcss.css create mode 100644 core/themes/claro/css/src/components/form--field-multiple.css create mode 100644 core/themes/claro/css/src/components/form--field-multiple.pcss.css create mode 100644 core/themes/claro/css/src/components/form--managed-file.css create mode 100644 core/themes/claro/css/src/components/form--managed-file.pcss.css create mode 100644 core/themes/claro/css/src/components/form--password-confirm.css create mode 100644 core/themes/claro/css/src/components/form--password-confirm.pcss.css create mode 100644 core/themes/claro/css/src/components/form--select.css create mode 100644 core/themes/claro/css/src/components/form--select.pcss.css create mode 100644 core/themes/claro/css/src/components/form--text.css create mode 100644 core/themes/claro/css/src/components/form--text.pcss.css create mode 100644 core/themes/claro/css/src/components/form.css create mode 100644 core/themes/claro/css/src/components/form.pcss.css create mode 100644 core/themes/claro/css/src/components/help.css create mode 100644 core/themes/claro/css/src/components/help.pcss.css create mode 100644 core/themes/claro/css/src/components/image-preview.css create mode 100644 core/themes/claro/css/src/components/image-preview.pcss.css create mode 100644 core/themes/claro/css/src/components/jquery.ui/theme.css create mode 100644 core/themes/claro/css/src/components/jquery.ui/theme.pcss.css create mode 100644 core/themes/claro/css/src/components/media.css create mode 100644 core/themes/claro/css/src/components/media.pcss.css create mode 100644 core/themes/claro/css/src/components/menus-and-lists.css create mode 100644 core/themes/claro/css/src/components/menus-and-lists.pcss.css create mode 100644 core/themes/claro/css/src/components/messages.css create mode 100644 core/themes/claro/css/src/components/messages.pcss.css create mode 100644 core/themes/claro/css/src/components/modules-page.css create mode 100644 core/themes/claro/css/src/components/modules-page.pcss.css create mode 100644 core/themes/claro/css/src/components/node.css create mode 100644 core/themes/claro/css/src/components/node.pcss.css create mode 100644 core/themes/claro/css/src/components/page-title.css create mode 100644 core/themes/claro/css/src/components/page-title.pcss.css create mode 100644 core/themes/claro/css/src/components/pager.css create mode 100644 core/themes/claro/css/src/components/pager.pcss.css create mode 100644 core/themes/claro/css/src/components/progress.css create mode 100644 core/themes/claro/css/src/components/progress.pcss.css create mode 100644 core/themes/claro/css/src/components/quickedit.css create mode 100644 core/themes/claro/css/src/components/quickedit.pcss.css create mode 100644 core/themes/claro/css/src/components/search-admin-settings.css create mode 100644 core/themes/claro/css/src/components/search-admin-settings.pcss.css create mode 100644 core/themes/claro/css/src/components/shortcut.css create mode 100644 core/themes/claro/css/src/components/shortcut.pcss.css create mode 100644 core/themes/claro/css/src/components/skip-link.css create mode 100644 core/themes/claro/css/src/components/skip-link.pcss.css create mode 100644 core/themes/claro/css/src/components/system-admin--admin-list.css create mode 100644 core/themes/claro/css/src/components/system-admin--admin-list.pcss.css create mode 100644 core/themes/claro/css/src/components/system-admin--links.css create mode 100644 core/themes/claro/css/src/components/system-admin--links.pcss.css create mode 100644 core/themes/claro/css/src/components/system-admin--modules.css create mode 100644 core/themes/claro/css/src/components/system-admin--modules.pcss.css create mode 100644 core/themes/claro/css/src/components/system-admin--panel.css create mode 100644 core/themes/claro/css/src/components/system-admin--panel.pcss.css create mode 100644 core/themes/claro/css/src/components/system-admin--status-report.css create mode 100644 core/themes/claro/css/src/components/system-admin--status-report.pcss.css create mode 100644 core/themes/claro/css/src/components/system-status-counter.css create mode 100644 core/themes/claro/css/src/components/system-status-counter.pcss.css create mode 100644 core/themes/claro/css/src/components/system-status-report-counters.css create mode 100644 core/themes/claro/css/src/components/system-status-report-counters.pcss.css create mode 100644 core/themes/claro/css/src/components/system-status-report-general-info.css create mode 100644 core/themes/claro/css/src/components/system-status-report-general-info.pcss.css create mode 100644 core/themes/claro/css/src/components/system-status-report.css create mode 100644 core/themes/claro/css/src/components/system-status-report.pcss.css create mode 100644 core/themes/claro/css/src/components/table--file-multiple-widget.css create mode 100644 core/themes/claro/css/src/components/table--file-multiple-widget.pcss.css create mode 100644 core/themes/claro/css/src/components/tabledrag.css create mode 100644 core/themes/claro/css/src/components/tabledrag.pcss.css create mode 100644 core/themes/claro/css/src/components/tables.css create mode 100644 core/themes/claro/css/src/components/tables.pcss.css create mode 100644 core/themes/claro/css/src/components/tableselect.css create mode 100644 core/themes/claro/css/src/components/tableselect.pcss.css create mode 100644 core/themes/claro/css/src/components/tablesort-indicator.css create mode 100644 core/themes/claro/css/src/components/tablesort-indicator.pcss.css create mode 100644 core/themes/claro/css/src/components/tabs.css create mode 100644 core/themes/claro/css/src/components/tabs.pcss.css create mode 100644 core/themes/claro/css/src/components/tour.theme.css create mode 100644 core/themes/claro/css/src/components/tour.theme.pcss.css create mode 100644 core/themes/claro/css/src/components/vertical-tabs.css create mode 100644 core/themes/claro/css/src/components/vertical-tabs.pcss.css create mode 100644 core/themes/claro/css/src/components/views-exposed-form.css create mode 100644 core/themes/claro/css/src/components/views-exposed-form.pcss.css create mode 100644 core/themes/claro/css/src/components/views-ui.css create mode 100644 core/themes/claro/css/src/components/views-ui.pcss.css create mode 100644 core/themes/claro/css/src/layout/breadcrumb.css create mode 100644 core/themes/claro/css/src/layout/breadcrumb.pcss.css create mode 100644 core/themes/claro/css/src/layout/card-list.css create mode 100644 core/themes/claro/css/src/layout/card-list.pcss.css create mode 100644 core/themes/claro/css/src/layout/image-widget.css create mode 100644 core/themes/claro/css/src/layout/image-widget.pcss.css create mode 100644 core/themes/claro/css/src/layout/layout.css create mode 100644 core/themes/claro/css/src/layout/layout.pcss.css create mode 100644 core/themes/claro/css/src/layout/local-actions.css create mode 100644 core/themes/claro/css/src/layout/local-actions.pcss.css create mode 100644 core/themes/claro/css/src/layout/node-add.css create mode 100644 core/themes/claro/css/src/layout/node-add.pcss.css create mode 100644 core/themes/claro/css/src/layout/system-admin--layout.css create mode 100644 core/themes/claro/css/src/layout/system-admin--layout.pcss.css create mode 100644 core/themes/claro/css/src/theme/ckeditor-dialog.css create mode 100644 core/themes/claro/css/src/theme/ckeditor-dialog.pcss.css create mode 100644 core/themes/claro/css/src/theme/ckeditor-editor.css create mode 100644 core/themes/claro/css/src/theme/ckeditor-editor.pcss.css create mode 100644 core/themes/claro/css/src/theme/ckeditor-frame.css create mode 100644 core/themes/claro/css/src/theme/ckeditor-frame.pcss.css create mode 100644 core/themes/claro/css/src/theme/colors.css create mode 100644 core/themes/claro/css/src/theme/colors.pcss.css create mode 100644 core/themes/claro/css/src/theme/field-ui.admin.css create mode 100644 core/themes/claro/css/src/theme/field-ui.admin.pcss.css create mode 100644 core/themes/claro/css/src/theme/filter.theme.css create mode 100644 core/themes/claro/css/src/theme/filter.theme.pcss.css create mode 100644 core/themes/claro/css/src/theme/install-page.css create mode 100644 core/themes/claro/css/src/theme/install-page.pcss.css create mode 100644 core/themes/claro/css/src/theme/maintenance-page.css create mode 100644 core/themes/claro/css/src/theme/maintenance-page.pcss.css create mode 100644 core/themes/claro/css/src/theme/views_ui.admin.theme.css create mode 100644 core/themes/claro/css/src/theme/views_ui.admin.theme.pcss.css create mode 100644 core/themes/claro/images/core/0074bd/chevron-left.svg create mode 100644 core/themes/claro/images/core/0074bd/chevron-right.svg create mode 100644 core/themes/claro/images/core/333333/caret-down.svg create mode 100644 core/themes/claro/images/core/73b355/check.svg create mode 100644 core/themes/claro/images/core/787878/cog.svg create mode 100644 core/themes/claro/images/core/787878/key.svg create mode 100644 core/themes/claro/images/core/787878/questionmark-disc.svg create mode 100644 core/themes/claro/images/core/README.md create mode 100644 core/themes/claro/images/core/cccccc/clock.svg create mode 100644 core/themes/claro/images/core/cccccc/d8-logo.svg create mode 100644 core/themes/claro/images/core/cccccc/database.svg create mode 100644 core/themes/claro/images/core/cccccc/php-logo.svg create mode 100644 core/themes/claro/images/core/cccccc/server.svg create mode 100644 core/themes/claro/images/core/e29700/warning.svg create mode 100644 core/themes/claro/images/core/e32700/error.svg create mode 100644 core/themes/claro/images/core/ee0000/required.svg create mode 100644 core/themes/claro/images/core/ffffff/ex.svg create mode 100644 core/themes/claro/images/core/stable/views_ui/sprites.png create mode 100644 core/themes/claro/images/noise-low.png create mode 100644 core/themes/claro/images/shortcut/favstar-rtl.svg create mode 100644 core/themes/claro/images/shortcut/favstar.svg create mode 100644 core/themes/claro/images/spinner-ltr.gif create mode 100644 core/themes/claro/images/spinner-rtl.gif create mode 100644 core/themes/claro/images/src/arrow-down.svg create mode 100644 core/themes/claro/images/src/arrow-left.svg create mode 100644 core/themes/claro/images/src/arrow-right.svg create mode 100644 core/themes/claro/images/src/arrow-up.svg create mode 100644 core/themes/claro/images/src/checkmark.svg create mode 100644 core/themes/claro/images/src/cog.svg create mode 100644 core/themes/claro/images/src/ex.svg create mode 100644 core/themes/claro/images/src/hamburger-menu.svg create mode 100644 core/themes/claro/images/src/hide.svg create mode 100644 core/themes/claro/images/src/magnifier-rtl.svg create mode 100644 core/themes/claro/images/src/magnifier.svg create mode 100644 core/themes/claro/images/src/message--error.svg create mode 100644 core/themes/claro/images/src/message--status.svg create mode 100644 core/themes/claro/images/src/message--warning.svg create mode 100644 core/themes/claro/images/src/plus.svg create mode 100644 core/themes/claro/images/src/select.svg create mode 100644 core/themes/claro/images/src/show.svg create mode 100644 core/themes/claro/images/src/sort--asc.svg create mode 100644 core/themes/claro/images/src/sort--desc.svg create mode 100644 core/themes/claro/images/src/sort--inactive--ltr.svg create mode 100644 core/themes/claro/images/src/sort--inactive--rtl.svg create mode 100644 core/themes/claro/images/src/tabledrag-handle.svg create mode 100644 core/themes/claro/images/src/tabledrag-tree.svg create mode 100644 core/themes/claro/images/src/trash.svg create mode 100644 core/themes/claro/images/ui-icons-222222-256x240.png create mode 100644 core/themes/claro/images/ui-icons-454545-256x240.png create mode 100644 core/themes/claro/images/ui-icons-800000-256x240.png create mode 100644 core/themes/claro/images/ui-icons-888888-256x240.png create mode 100644 core/themes/claro/images/ui-icons-ffffff-256x240.png create mode 100644 core/themes/claro/js/ajax.es6.js create mode 100644 core/themes/claro/js/ajax.js create mode 100644 core/themes/claro/js/autocomplete.es6.js create mode 100644 core/themes/claro/js/autocomplete.js create mode 100644 core/themes/claro/js/checkbox.es6.js create mode 100644 core/themes/claro/js/checkbox.js create mode 100644 core/themes/claro/js/details.es6.js create mode 100644 core/themes/claro/js/details.js create mode 100644 core/themes/claro/js/dropbutton.es6.js create mode 100644 core/themes/claro/js/dropbutton.js create mode 100644 core/themes/claro/js/messages.es6.js create mode 100644 core/themes/claro/js/messages.js create mode 100644 core/themes/claro/js/mobile.install.es6.js create mode 100644 core/themes/claro/js/mobile.install.js create mode 100644 core/themes/claro/js/nav-tabs.es6.js create mode 100644 core/themes/claro/js/nav-tabs.js create mode 100644 core/themes/claro/js/responsive-details.es6.js create mode 100644 core/themes/claro/js/responsive-details.js create mode 100644 core/themes/claro/js/tabledrag.es6.js create mode 100644 core/themes/claro/js/tabledrag.js create mode 100644 core/themes/claro/js/user.es6.js create mode 100644 core/themes/claro/js/user.js create mode 100644 core/themes/claro/js/vertical-tabs.es6.js create mode 100644 core/themes/claro/js/vertical-tabs.js create mode 100644 core/themes/claro/logo.svg create mode 100644 core/themes/claro/screenshot.png create mode 100644 core/themes/claro/src/ClaroPreRender.php create mode 100644 core/themes/claro/templates/admin/admin-block-content.html.twig create mode 100644 core/themes/claro/templates/admin/admin-page.html.twig create mode 100644 core/themes/claro/templates/admin/config_translation_manage_form_element.html.twig create mode 100644 core/themes/claro/templates/admin/indentation.html.twig create mode 100644 core/themes/claro/templates/admin/tablesort-indicator.html.twig create mode 100644 core/themes/claro/templates/admin/update-version.html.twig create mode 100644 core/themes/claro/templates/admin/views-ui-views-listing-table.html.twig create mode 100644 core/themes/claro/templates/block--local-actions-block.html.twig create mode 100644 core/themes/claro/templates/block-content-add-list.html.twig create mode 100644 core/themes/claro/templates/block/block--local-tasks-block.html.twig create mode 100644 core/themes/claro/templates/breadcrumb.html.twig create mode 100644 core/themes/claro/templates/content-edit/file-managed-file.html.twig create mode 100644 core/themes/claro/templates/content-edit/file-widget-multiple.html.twig create mode 100644 core/themes/claro/templates/content-edit/image-widget.html.twig create mode 100644 core/themes/claro/templates/datetime-form.html.twig create mode 100644 core/themes/claro/templates/datetime-wrapper.html.twig create mode 100644 core/themes/claro/templates/details.html.twig create mode 100644 core/themes/claro/templates/entity-add-list.html.twig create mode 100644 core/themes/claro/templates/field/file-link.html.twig create mode 100644 core/themes/claro/templates/fieldset.html.twig create mode 100644 core/themes/claro/templates/filter/filter-guidelines.html.twig create mode 100644 core/themes/claro/templates/filter/filter-tips.html.twig create mode 100644 core/themes/claro/templates/form-element-label.html.twig create mode 100644 core/themes/claro/templates/form-element.html.twig create mode 100644 core/themes/claro/templates/form/field-multiple-value-form.html.twig create mode 100644 core/themes/claro/templates/form/input.html.twig create mode 100644 core/themes/claro/templates/install-page.html.twig create mode 100644 core/themes/claro/templates/maintenance-page.html.twig create mode 100644 core/themes/claro/templates/menu-local-tasks.html.twig create mode 100644 core/themes/claro/templates/misc/status-messages.html.twig create mode 100644 core/themes/claro/templates/navigation/details--vertical-tabs.html.twig create mode 100644 core/themes/claro/templates/navigation/menu-local-task--views-ui.html.twig create mode 100644 core/themes/claro/templates/navigation/menu-local-task.html.twig create mode 100644 core/themes/claro/templates/node-add-list.html.twig create mode 100644 core/themes/claro/templates/node-edit-form.html.twig create mode 100644 core/themes/claro/templates/off-canvas-page-wrapper.html.twig create mode 100644 core/themes/claro/templates/page.html.twig create mode 100644 core/themes/claro/templates/pager.html.twig create mode 100644 core/themes/claro/templates/region--breadcrumb.html.twig create mode 100644 core/themes/claro/templates/status-report-counter.html.twig create mode 100644 core/themes/claro/templates/status-report-general-info.html.twig create mode 100644 core/themes/claro/templates/status-report-grouped.html.twig create mode 100644 core/themes/claro/templates/status-report-page.html.twig create mode 100644 core/themes/claro/templates/system-themes-page.html.twig create mode 100644 core/themes/claro/templates/text-format-wrapper.html.twig create mode 100644 core/themes/claro/templates/views-exposed-form.html.twig create mode 100644 core/themes/claro/templates/views/views-mini-pager.html.twig diff --git a/core/.stylelintignore b/core/.stylelintignore new file mode 100644 index 0000000000..d70b0031c4 --- /dev/null +++ b/core/.stylelintignore @@ -0,0 +1,2 @@ +themes/claro/**/*.css +!themes/claro/**/*.pcss.css diff --git a/core/composer.json b/core/composer.json index 5ef9c6d92d..438d4312b3 100644 --- a/core/composer.json +++ b/core/composer.json @@ -65,6 +65,7 @@ "drupal/book": "self.version", "drupal/breakpoint": "self.version", "drupal/ckeditor": "self.version", + "drupal/claro": "self.version", "drupal/classy": "self.version", "drupal/color": "self.version", "drupal/comment": "self.version", diff --git a/core/modules/shortcut/shortcut.install b/core/modules/shortcut/shortcut.install index 21ebf51942..ba1c700551 100644 --- a/core/modules/shortcut/shortcut.install +++ b/core/modules/shortcut/shortcut.install @@ -52,8 +52,13 @@ function shortcut_schema() { function shortcut_install() { // Theme settings are not configuration entities and cannot depend on modules // so to set a module-specific setting, we need to set it with logic. - if (\Drupal::service('theme_handler')->themeExists('seven')) { - \Drupal::configFactory()->getEditable('seven.settings')->set('third_party_settings.shortcut.module_link', TRUE)->save(TRUE); + foreach (['seven', 'claro'] as $theme) { + if (\Drupal::service('theme_handler')->themeExists($theme)) { + \Drupal::configFactory() + ->getEditable("$theme.settings") + ->set('third_party_settings.shortcut.module_link', TRUE) + ->save(TRUE); + } } } @@ -63,7 +68,12 @@ function shortcut_install() { function shortcut_uninstall() { // Theme settings are not configuration entities and cannot depend on modules // so to unset a module-specific setting, we need to unset it with logic. - if (\Drupal::service('theme_handler')->themeExists('seven')) { - \Drupal::configFactory()->getEditable('seven.settings')->clear('third_party_settings.shortcut')->save(TRUE); + foreach (['seven', 'claro'] as $theme) { + if (\Drupal::service('theme_handler')->themeExists($theme)) { + \Drupal::configFactory() + ->getEditable("$theme.settings") + ->clear('third_party_settings.shortcut') + ->save(TRUE); + } } } diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module index 1b281b2b02..1c5e74b7f7 100644 --- a/core/modules/shortcut/shortcut.module +++ b/core/modules/shortcut/shortcut.module @@ -435,11 +435,13 @@ function shortcut_toolbar() { * Implements hook_themes_installed(). */ function shortcut_themes_installed($theme_list) { - if (in_array('seven', $theme_list)) { - // Theme settings are not configuration entities and cannot depend on modules - // so to set a module-specific setting, we need to set it with logic. - if (\Drupal::moduleHandler()->moduleExists('shortcut')) { - \Drupal::configFactory()->getEditable('seven.settings')->set('third_party_settings.shortcut.module_link', TRUE)->save(TRUE); + // Theme settings are not configuration entities and cannot depend on modules + // so to set a module-specific setting, we need to set it with logic. + foreach (['seven', 'claro'] as $theme) { + if (in_array($theme, $theme_list, TRUE)) { + \Drupal::configFactory()->getEditable("$theme.settings") + ->set('third_party_settings.shortcut.module_link', TRUE) + ->save(TRUE); } } } diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php new file mode 100644 index 0000000000..e456ecbefa --- /dev/null +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroBlockFilterTest.php @@ -0,0 +1,36 @@ +container->get('theme_installer')->install(['claro']); + $this->config('system.theme')->set('default', 'claro')->save(); + } + +} diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php new file mode 100644 index 0000000000..3415765086 --- /dev/null +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroEntityDisplayTest.php @@ -0,0 +1,116 @@ +container->get('theme_installer')->install(['claro']); + $this->config('system.theme')->set('default', 'claro')->save(); + } + + /** + * Copied from parent. + * + * This is Drupal\Tests\field_ui\FunctionalJavascript\EntityDisplayTest::testEntityForm() + * with a line changed to reflect row weight toggle being a link instead + * of a button. + */ + public function testEntityForm() { + $this->drupalGet('entity_test/manage/1/edit'); + $this->assertSession()->fieldExists('field_test_text[0][value]'); + + $this->drupalGet('entity_test/structure/entity_test/form-display'); + $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'content')->isSelected()); + $this->getSession()->getPage()->clickLink('Show row weights'); + $this->assertSession()->waitForElementVisible('css', '[name="fields[field_test_text][region]"]'); + $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'hidden'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'hidden')->isSelected()); + + $this->submitForm([], 'Save'); + $this->assertSession()->pageTextContains('Your settings have been saved.'); + $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'hidden')->isSelected()); + + $this->drupalGet('entity_test/manage/1/edit'); + $this->assertSession()->fieldNotExists('field_test_text[0][value]'); + } + + /** + * Copied from parent. + * + * This is Drupal\Tests\field_ui\FunctionalJavascript\EntityDisplayTest::testEntityView() + * with a line changed to reflect row weight toggle being a link instead + * of a button. + */ + public function testEntityView() { + $this->drupalGet('entity_test/1'); + $this->assertSession()->elementNotExists('css', '.field--name-field-test-text'); + + $this->drupalGet('entity_test/structure/entity_test/display'); + $this->assertSession()->elementExists('css', '.region-content-message.region-empty'); + $this->getSession()->getPage()->clickLink('Show row weights'); + $this->assertSession()->waitForElementVisible('css', '[name="fields[field_test_text][region]"]'); + $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'hidden')->isSelected()); + + $this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'content'); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'content')->isSelected()); + + $this->submitForm([], 'Save'); + $this->assertSession()->pageTextContains('Your settings have been saved.'); + $this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'content')->isSelected()); + + $this->drupalGet('entity_test/1'); + $this->assertSession()->elementExists('css', '.field--name-field-test-text'); + } + + /** + * Copied from parent. + * + * This is Drupal\Tests\field_ui\FunctionalJavascript\EntityDisplayTest::testExtraFields() + * with a line changed to reflect Claro's tabledrag selector. + */ + public function testExtraFields() { + entity_test_create_bundle('bundle_with_extra_fields'); + $this->drupalGet('entity_test/structure/bundle_with_extra_fields/display'); + $this->assertSession()->waitForElement('css', '.tabledrag-handle'); + $id = $this->getSession()->getPage()->find('css', '[name="form_build_id"]')->getValue(); + + $extra_field_row = $this->getSession()->getPage()->find('css', '#display-extra-field'); + $disabled_region_row = $this->getSession()->getPage()->find('css', '.region-hidden-title'); + + $extra_field_row->find('css', '.js-tabledrag-handle')->dragTo($disabled_region_row); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->assertSession() + ->waitForElement('css', "[name='form_build_id']:not([value='$id'])"); + + $this->submitForm([], 'Save'); + $this->assertSession()->pageTextContains('Your settings have been saved.'); + } + +} diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php new file mode 100644 index 0000000000..500c239aaa --- /dev/null +++ b/core/tests/Drupal/FunctionalJavascriptTests/Theme/ClaroMenuUiJavascriptTest.php @@ -0,0 +1,41 @@ +container->get('theme_installer')->install(['claro']); + $this->config('system.theme')->set('default', 'claro')->save(); + } + + /** + * Intentionally empty method. + * + * Contextual links do not work in admin themes, so this is empty to prevent + * this test running in the parent class. + */ + public function testBlockContextualLinks() { + } + +} diff --git a/core/tests/Drupal/FunctionalTests/Theme/ClaroTest.php b/core/tests/Drupal/FunctionalTests/Theme/ClaroTest.php new file mode 100644 index 0000000000..ad052c5c41 --- /dev/null +++ b/core/tests/Drupal/FunctionalTests/Theme/ClaroTest.php @@ -0,0 +1,75 @@ +assertTrue(\Drupal::service('theme_installer')->install(['claro'])); + $this->container->get('config.factory') + ->getEditable('system.theme') + ->set('default', 'claro') + ->save(); + } + + /** + * Tests that the Claro theme always adds its elements.css. + * + * @see claro.info.yml + */ + public function testRegressionMissingElementsCss() { + $this->drupalGet(''); + $this->assertSession()->statusCodeEquals(200); + $this->assertSession()->responseContains('claro/css/src/base/elements.css'); + + $this->drupalLogin($this->rootUser); + $this->drupalGet('admin/modules'); + $this->assertSession()->elementNotExists('css', '#block-claro-help'); + + // Install the block module to ensure Claro's configuration is valid + // according to schema. + \Drupal::service('module_installer')->install(['block', 'help']); + $this->rebuildAll(); + + $this->drupalGet('admin/modules'); + $this->assertSession()->elementExists('css', '#block-claro-help'); + } + + /** + * Tests that the Claro theme can be uninstalled, despite being experimental. + * + * @todo Remove in https://www.drupal.org/project/drupal/issues/3066007 + */ + public function testIsUninstallable() { + $this->drupalLogin($this->drupalCreateUser(['access administration pages', 'administer themes'])); + + $this->drupalGet('admin/appearance'); + $this->cssSelect('a[title="Install Seven as default theme"]')[0]->click(); + $this->cssSelect('a[title="Uninstall Claro theme"]')[0]->click(); + $this->assertText('The Claro theme has been uninstalled.'); + } + +} diff --git a/core/themes/claro/claro.info.yml b/core/themes/claro/claro.info.yml new file mode 100644 index 0000000000..55f43b606c --- /dev/null +++ b/core/themes/claro/claro.info.yml @@ -0,0 +1,175 @@ +# This theme is marked as @internal. It is intended to evolve and change over +# minor releases. +# Change record https://www.drupal.org/node/2582945. +# As the UI of Drupal improves between minor versions, the markup and assets +# in the Claro theme will change. The Claro theme is not backwards compatible. +# If you wish to modify the output or assets of Claro you can: +# 1. Copy the whole of Claro and rename it as your own administration theme. You +# will need to manually manage your own updates if you want to stay up to +# date with Claro's bug fixes and feature support. +# +# 2. Sub-theme Claro. This is only recommended if you want to make minor tweaks +# and understand that Claro could break your modifications as it changes. +name: Claro +type: theme +base theme: classy +description: 'A clean, accessible, and powerful Drupal administration theme.' +alt text: 'Screenshot of Claro, Drupal administration theme.' +package: Core +version: VERSION +experimental: true +core: 8.x +libraries: + - claro/global-styling +libraries-override: + system/base: + css: + component: + /core/themes/stable/css/system/components/ajax-progress.module.css: css/src/components/ajax-progress.module.css + /core/themes/stable/css/system/components/autocomplete-loading.module.css: css/src/components/autocomplete-loading.module.css + /core/themes/stable/css/system/components/system-status-counter.css: css/src/components/system-status-counter.css + /core/themes/stable/css/system/components/system-status-report-counters.css: css/src/components/system-status-report-counters.css + /core/themes/stable/css/system/components/system-status-report-general-info.css: css/src/components/system-status-report-general-info.css + /core/themes/stable/css/system/components/tabledrag.module.css: css/src/components/tabledrag.css + + system/admin: + css: + theme: + /core/themes/stable/css/system/system.admin.css: false + + core/drupal.dropbutton: + css: + component: + /core/themes/stable/css/core/dropbutton/dropbutton.css: css/src/components/dropbutton.css + + core/drupal.tabledrag: + js: + misc/tabledrag.js: js/tabledrag.js + + core/drupal.vertical-tabs: + css: + component: + /core/themes/stable/css/core/vertical-tabs.css: false + js: + misc/vertical-tabs.js: js/vertical-tabs.js + + core/jquery.ui: + css: + theme: + assets/vendor/jquery.ui/themes/base/theme.css: false + + core/jquery.ui.dialog: + css: + component: + assets/vendor/jquery.ui/themes/base/dialog.css: false + + classy/dialog: claro/claro.drupal.dialog + + classy/base: + css: + component: + css/components/action-links.css: false + css/components/breadcrumb.css: false + css/components/button.css: false + css/components/details.css: false + css/components/dropbutton.css: false + css/components/form.css: false + css/components/tabs.css: false + css/components/pager.css: false + css/components/tableselect.css: css/src/components/tableselect.css + css/components/tabledrag.css: false + css/components/collapse-processed.css: false + + classy/dropbutton: + css: + component: + css/components/dropbutton.css: false + + classy/messages: + css: + component: + css/components/messages.css: false + + classy/progress: + css: + component: + css/components/progress.css: css/src/components/progress.css + # @todo Refactor when https://www.drupal.org/node/2642122 is fixed. + + classy/user: false + + user/drupal.user: claro/form.password-confirm + + field_ui/drupal.field_ui: + css: + theme: + /core/themes/stable/css/field_ui/field_ui.admin.css: css/src/theme/field-ui.admin.css + + filter/drupal.filter.admin: + css: + theme: + /core/themes/stable/css/filter/filter.admin.css: css/src/theme/filter.theme.css + + filter/drupal.filter: + css: + theme: + /core/themes/stable/css/filter/filter.admin.css: css/src/theme/filter.theme.css + + views_ui/admin.styling: + css: + theme: + /core/themes/stable/css/views_ui/views_ui.admin.theme.css: css/src/theme/views_ui.admin.theme.css + +libraries-extend: + ckeditor/drupal.ckeditor: + - claro/ckeditor-editor + classy/image-widget: + - claro/image-widget + core/ckeditor: + - claro/ckeditor-dialog + core/drupal.collapse: + - claro/details-focus + core/drupal.dropbutton: + - claro/dropbutton + core/drupal.checkbox: + - claro/checkbox + core/drupal.message: + - claro/messages + core/drupal.vertical-tabs: + - claro/vertical-tabs + core/jquery.ui: + - claro/claro.jquery.ui + file/drupal.file: + - claro/file + system/admin: + - claro/system.admin + core/drupal.autocomplete: + - claro/autocomplete + tour/tour-styling: + - claro/tour-styling + shortcut/drupal.shortcut: + - claro/drupal.shortcut + core/drupal.ajax: + - claro/ajax + views/views.module: + - claro/views + +quickedit_stylesheets: + - css/src/components/quickedit.css +ckeditor_stylesheets: + - css/src/base/elements.css + - css/src/base/typography.css + - css/src/theme/ckeditor-frame.css + +regions: + header: 'Header' + pre_content: 'Pre-content' + breadcrumb: Breadcrumb + highlighted: Highlighted + help: Help + content: Content + page_top: 'Page top' + page_bottom: 'Page bottom' + sidebar_first: 'First sidebar' +regions_hidden: + - sidebar_first diff --git a/core/themes/claro/claro.libraries.yml b/core/themes/claro/claro.libraries.yml new file mode 100644 index 0000000000..1ab96c8ca8 --- /dev/null +++ b/core/themes/claro/claro.libraries.yml @@ -0,0 +1,249 @@ +global-styling: + version: VERSION + css: + base: + css/src/base/elements.css: {} + css/src/base/typography.css: {} + css/src/base/print.css: {} + component: + css/src/components/accordion.css: {} + css/src/components/action-link.css: {} + css/src/components/content-header.css: {} + css/src/components/container-inline.css: {} + css/src/components/container-inline.module.css: {} + css/src/components/breadcrumb.css: {} + css/src/components/button.css: {} + css/src/components/details.css: {} + css/src/components/divider.css: {} + css/src/components/messages.css: {} + css/src/components/entity-meta.css: {} + css/src/components/fieldset.css: {} + css/src/components/form.css: {} + css/src/components/form--checkbox-radio.css: {} + css/src/components/form--checkbox-radio--ie.css: {} + css/src/components/form--field-multiple.css: {} + css/src/components/form--managed-file.css: {} + css/src/components/form--text.css: {} + css/src/components/form--select.css: {} + css/src/components/help.css: {} + css/src/components/image-preview.css: {} + css/src/components/menus-and-lists.css: {} + css/src/components/modules-page.css: {} + css/src/components/node.css: {} + css/src/components/page-title.css: {} + css/src/components/pager.css: {} + css/src/components/skip-link.css: {} + css/src/components/tables.css: {} + css/src/components/table--file-multiple-widget.css: {} + css/src/components/search-admin-settings.css: {} + css/src/components/tablesort-indicator.css: {} + css/src/components/system-status-report-general-info.css: {} + css/src/components/system-status-report.css: {} + css/src/components/system-status-report-counters.css: {} + css/src/components/system-status-counter.css: {} + css/src/components/tabs.css: {} + css/src/components/views-ui.css: {} + theme: + css/src/theme/colors.css: {} + layout: + css/src/layout/breadcrumb.css: {} + css/src/layout/local-actions.css: {} + css/src/layout/layout.css: {} + dependencies: + - system/admin + - core/jquery + - core/drupal + # Claro has small and extra small variation for most of the control elements + # such as inputs, action links, buttons, dropbuttons. For usability and + # accessibility reasons, we keep target sizes big enough on touch screen + # devices (by not making these elements smaller than their default size). + # Modernizr is used for recognising whether user is using a touch device or + # not. This allows conditionally rendering small variation of the control + # elements on non-touch devices. In some cases, such as when rendering + # links, it is hard recognize when Modernizr should be attached, therefore + # it has to be always attached. + - core/modernizr + +node-form: + version: VERSION + css: + layout: + css/src/layout/node-add.css: {} + dependencies: + - node/form + +maintenance-page: + version: VERSION + js: + js/mobile.install.js: {} + css: + theme: + css/src/theme/maintenance-page.css: {} + dependencies: + - system/maintenance + - claro/global-styling + +install-page: + version: VERSION + js: + js/mobile.install.js: {} + css: + theme: + css/src/theme/install-page.css: {} + dependencies: + - claro/maintenance-page + +drupal.nav-tabs: + version: VERSION + js: + js/nav-tabs.js: {} + dependencies: + - core/matchmedia + - core/jquery + - core/drupal + - core/jquery.once + - core/drupal.debounce + - core/collapse + +drupal.responsive-detail: + version: VERSION + js: + js/responsive-details.js: {} + dependencies: + - core/matchmedia + - core/matchmedia.addListener + - core/jquery + - core/jquery.once + - core/collapse + +claro.jquery.ui: + version: VERSION + css: + component: + css/src/components/jquery.ui/theme.css: { weight: -1 } + +claro.drupal.dialog: + version: VERSION + css: + theme: + css/src/components/dialog.css: {} + +ckeditor-dialog: + version: VERSION + css: + theme: + css/src/theme/ckeditor-dialog.css: {} + +ckeditor-editor: + version: VERSION + css: + theme: + css/src/theme/ckeditor-editor.css: {} + +tour-styling: + version: VERSION + css: + theme: + css/src/components/tour.theme.css: {} + +media-form: + version: VERSION + css: + layout: + css/src/components/media.css: {} + dependencies: + - media/form + +image-widget: + version: VERSION + css: + component: + css/src/layout/image-widget.css: {} + +system.admin: + version: VERSION + css: + component: + css/src/components/system-admin--admin-list.css: { weight: -10 } + css/src/components/system-admin--links.css: { weight: -10 } + css/src/components/system-admin--modules.css: { weight: -10 } + css/src/components/system-admin--panel.css: { weight: -10 } + css/src/components/system-admin--status-report.css: { weight: -10 } + layout: + css/src/layout/system-admin--layout.css: { weight: -10 } + dependencies: + - claro/card + +checkbox: + version: VERSION + js: + js/checkbox.js: {} + dependencies: + - core/drupal + +dropbutton: + version: VERSION + js: + js/dropbutton.js: {} + dependencies: + - core/drupal + +autocomplete: + version: VERSION + js: + js/autocomplete.js: {} + +drupal.shortcut: + version: VERSION + css: + # @todo move this to components after + # https://www.drupal.org/project/drupal/issues/3045467 is in. + theme: + css/src/components/shortcut.css: {} + +details-focus: + js: + js/details.js: {} + +ajax: + js: + js/ajax.js: {} + +form.password-confirm: + css: + component: + css/src/components/form--password-confirm.css: {} + js: + js/user.js: {} + dependencies: + - core/jquery + - core/drupal + - core/jquery.once + - claro/global-styling + +views: + css: + component: + css/src/components/views-exposed-form.css: {} + +messages: + js: + js/messages.js: {} + +card: + css: + component: + css/src/layout/card-list.css: {} + css/src/components/card.css: {} + +vertical-tabs: + css: + component: + css/src/components/vertical-tabs.css: {} + dependencies: + - claro/global-styling + +file: + css: + component: + css/src/components/file.css: {} diff --git a/core/themes/claro/claro.theme b/core/themes/claro/claro.theme new file mode 100644 index 0000000000..ccd1f348c4 --- /dev/null +++ b/core/themes/claro/claro.theme @@ -0,0 +1,1192 @@ + [ + 'claro/drupal.nav-tabs', + ], + ]; + } + elseif (!empty($variables['secondary'])) { + $variables['secondary']['#attached'] = [ + 'library' => [ + 'claro/drupal.nav-tabs', + ], + ]; + } + + foreach (Element::children($variables['primary']) as $key) { + $variables['primary'][$key]['#level'] = 'primary'; + } + foreach (Element::children($variables['secondary']) as $key) { + $variables['secondary'][$key]['#level'] = 'secondary'; + } +} + +/** + * Implements hook_preprocess_HOOK() for menu-local-task templates. + */ +function claro_preprocess_menu_local_task(&$variables) { + $variables['link']['#options']['attributes']['class'][] = 'tabs__link'; + $variables['link']['#options']['attributes']['class'][] = 'js-tabs-link'; + + // Ensure is-active class is set when the tab is active. The generic active + // link handler applies stricter comparison rules than what is necessary for + // tabs. + if (isset($variables['is_active']) && $variables['is_active'] === TRUE) { + $variables['link']['#options']['attributes']['class'][] = 'is-active'; + } + + if (isset($variables['element']['#level'])) { + $variables['level'] = $variables['element']['#level']; + } +} + +/** + * Implements hook_preprocess_HOOK() for menu-local-task Views UI templates. + */ +function claro_preprocess_menu_local_task__views_ui(&$variables) { + // Remove 'tabs__link' without adding a new class because it couldn't be used + // reliably. + // @see https://www.drupal.org/node/3051605 + $link_class_index = array_search('tabs__link', $variables['link']['#options']['attributes']['class']); + if ($link_class_index !== FALSE) { + unset($variables['link']['#options']['attributes']['class'][$link_class_index]); + } +} + +/** + * Implements template_preprocess_HOOK() for node_add_list. + * + * Makes node_add_list variables compatible with entity_add_list. + */ +function claro_preprocess_node_add_list(&$variables) { + if (!empty($variables['content'])) { + /** @var \Drupal\node\NodeTypeInterface $type */ + foreach ($variables['content'] as $type) { + $label = $type->label(); + $description = $type->getDescription(); + $type_id = $type->id(); + $add_url = Url::fromRoute('node.add', ['node_type' => $type_id]); + $variables['bundles'][$type_id] = [ + 'label' => $label, + 'add_link' => Link::fromTextAndUrl($label, $add_url), + 'description' => [], + ]; + if (!empty($description)) { + $variables['bundles'][$type_id]['description'] = [ + '#markup' => $description, + ]; + } + } + $variables['attributes']['class'][] = 'node-type-list'; + } +} + +/** + * Implements template_preprocess_HOOK() for block_content_add_list. + * + * Makes block_content_add_list variables compatible with entity_add_list. + */ +function claro_preprocess_block_content_add_list(&$variables) { + if (!empty($variables['content'])) { + $query = \Drupal::request()->query->all(); + /** @var \Drupal\block_content\BlockContentTypeInterface $type */ + foreach ($variables['content'] as $type) { + $label = $type->label(); + $description = $type->getDescription(); + $type_id = $type->id(); + $add_url = Url::fromRoute('block_content.add_form', [ + 'block_content_type' => $type_id, + ], [ + 'query' => $query, + ]); + $variables['bundles'][$type_id] = [ + 'label' => $label, + 'add_link' => Link::fromTextAndUrl($label, $add_url), + 'description' => [], + ]; + + if (!empty($description)) { + $variables['bundles'][$type_id]['description'] = [ + '#markup' => $description, + ]; + } + } + } +} + +/** + * Implements template_preprocess_HOOK() for entity_add_list. + */ +function claro_preprocess_entity_add_list(&$variables) { + // Remove description if empty. + foreach ($variables['bundles'] as $type_id => $values) { + if (isset($values['description']['#markup']) && empty($values['description']['#markup'])) { + $variables['bundles'][$type_id]['description'] = []; + } + } +} + +/** + * Implements hook_preprocess_block() for block content. + * + * Disables contextual links for all blocks. + */ +function claro_preprocess_block(&$variables) { + if (isset($variables['title_suffix']['contextual_links'])) { + unset($variables['title_suffix']['contextual_links']); + unset($variables['elements']['#contextual_links']); + + $variables['attributes']['class'] = array_diff($variables['attributes']['class'], ['contextual-region']); + } +} + +/** + * Implements template_preprocess_HOOK() for admin_block. + */ +function claro_preprocess_admin_block(&$variables) { + if (!empty($variables['block']['content'])) { + $variables['block']['content']['#attributes']['class'][] = 'admin-list--panel'; + } +} + +/** + * Implements template_preprocess_HOOK() for admin_block. + */ +function claro_preprocess_admin_block_content(&$variables) { + foreach ($variables['content'] as &$item) { + $link_attributes = $item['url']->getOption('attributes') ?: []; + $link_attributes['class'][] = 'admin-item__link'; + $item['url']->setOption('attributes', $link_attributes); + $item['link'] = Link::fromTextAndUrl($item['title'], $item['url']); + + if (empty($item['description']) || empty($item['description']['#markup'])) { + unset($item['description']); + } + } +} + +/** + * Implements hook_preprocess_HOOK() for menu-local-action templates. + */ +function claro_preprocess_menu_local_action(array &$variables) { + $variables['link']['#options']['attributes']['class'][] = 'button--primary'; + $variables['attributes']['class'][] = 'local-actions__item'; + $legacy_class_key = array_search('button-action', $variables['link']['#options']['attributes']['class']); + + if ($legacy_class_key !== FALSE) { + $variables['link']['#options']['attributes']['class'][$legacy_class_key] = 'button--action'; + } +} + +/** + * Implements hook_element_info_alter(). + */ +function claro_element_info_alter(&$type) { + // Add a pre-render function that handles the sidebar of the node form. + // @todo Refactor when https://www.drupal.org/node/3056089 is in. + if (isset($type['container'])) { + $container_pre_renders = !empty($type['container']['#pre_render']) ? $type['container']['#pre_render'] : []; + array_unshift($container_pre_renders, [ClaroPreRender::class, 'container']); + + $type['container']['#pre_render'] = $container_pre_renders; + } + + // @todo Refactor when https://www.drupal.org/node/3016343 is fixed. + if (isset($type['text_format'])) { + $type['text_format']['#pre_render'][] = [ClaroPreRender::class, 'textFormat']; + } + + // Add a pre-render function that handles dropbutton variants. + if (isset($type['dropbutton'])) { + $type['dropbutton']['#pre_render'][] = [ClaroPreRender::class, 'dropButton']; + } + + if (isset($type['vertical_tabs'])) { + $type['vertical_tabs']['#pre_render'][] = [ClaroPreRender::class, 'verticalTabs']; + } + + // Add a pre-render to managed_file. + if (isset($type['managed_file'])) { + $type['managed_file']['#pre_render'][] = [ClaroPreRender::class, 'managedFile']; + } +} + +/** + * Implements template_preprocess_filter_guidelines(). + */ +function claro_preprocess_filter_guidelines(&$variables) { + // Fix filter guidelines selector issue of 'filter/drupal.filter'. + // @todo Remove when https://www.drupal.org/node/2881212 is fixed. + $variables['attributes']['class'][] = 'filter-guidelines-item'; + $variables['attributes']['class'][] = 'filter-guidelines-' . $variables['format']->id(); +} + +/** + * Implements template_preprocess_text_format_wrapper(). + * + * @todo Remove when https://www.drupal.org/node/3016343 is fixed. + */ +function claro_preprocess_text_format_wrapper(&$variables) { + $description_attributes = []; + if (!empty($variables['attributes']['id'])) { + $description_attributes['id'] = $variables['attributes']['aria-describedby'] = $variables['attributes']['id']; + unset($variables['attributes']['id']); + } + $variables['description_attributes'] = new Attribute($description_attributes); +} + +/** + * Implements hook_theme_registry_alter(). + */ +function claro_theme_registry_alter(&$theme_registry) { + if (!empty($theme_registry['admin_block_content'])) { + $theme_registry['admin_block_content']['variables']['attributes'] = []; + } + + // @todo Remove when https://www.drupal.org/node/3016346 is fixed. + if (!empty($theme_registry['text_format_wrapper'])) { + $theme_registry['text_format_wrapper']['variables']['disabled'] = FALSE; + } +} + +/** + * Implements hook_preprocess_install_page(). + */ +function claro_preprocess_install_page(&$variables) { + // Claro has custom styling for the install page. + $variables['#attached']['library'][] = 'claro/install-page'; +} + +/** + * Implements hook_preprocess_maintenance_page(). + */ +function claro_preprocess_maintenance_page(&$variables) { + // Claro has custom styling for the maintenance page. + $variables['#attached']['library'][] = 'claro/maintenance-page'; +} + +/** + * Implements hook_preprocess_HOOK() for details. + * + * @todo Revisit when https://www.drupal.org/node/3056089 is in. + */ +function claro_preprocess_details(&$variables) { + $element = $variables['element']; + + if (!empty($element['#accordion_item'])) { + // Details should appear as an accordion item. + $variables['accordion_item'] = TRUE; + } + + if (!empty($element['#accordion'])) { + // Details should appear as a standalone accordion. + $variables['accordion'] = TRUE; + } + + if (!empty($element['#theme']) && $element['#theme'] === 'file_widget_multiple') { + // Mark the details required if needed. If the file widget allows uploading + // multiple files, the required state is checked by checking the state of + // the first child. + $variables['required'] = isset($element[0]['#required']) ? + $element[0]['#required'] : + !empty($element['#required']); + + // If the error is the same as the one in the multiple field widget element, + // we have to avoid displaying it twice. Seven and Stark have this issue + // as well. + // @todo revisit when https://www.drupal.org/node/3084906 is fixed. + if (isset($element['#errors']) && isset($variables['errors']) && $element['#errors'] === $variables['errors']) { + unset($variables['errors']); + } + } + + $variables['disabled'] = !empty($element['#disabled']); +} + +/** + * Implements hook_form_alter(). + */ +function claro_form_alter(&$form, FormStateInterface $form_state) { + $build_info = $form_state->getBuildInfo(); + + // Make entity forms delete link use the action-link component. + if (isset($form['actions']['delete']['#type']) && $form['actions']['delete']['#type'] === 'link' && !empty($build_info['callback_object']) && $build_info['callback_object'] instanceof EntityForm) { + $form['actions']['delete'] = _claro_convert_link_to_action_link($form['actions']['delete'], 'trash', 'default', 'danger'); + } +} + +/** + * Implements hook_preprocess_HOOK() for links. + */ +function claro_preprocess_links(&$variables) { + foreach ($variables['links'] as $links_item) { + if (!empty($links_item['link']) && !empty($links_item['link']['#url']) && $links_item['link']['#url'] instanceof Url) { + if ($links_item['link']['#url']->isRouted()) { + switch ($links_item['link']['#url']->getRouteName()) { + case 'system.theme_settings_theme': + $links_item['link'] = _claro_convert_link_to_action_link($links_item['link'], 'cog', 'small'); + break; + + case 'system.theme_uninstall': + $links_item['link'] = _claro_convert_link_to_action_link($links_item['link'], 'ex', 'small'); + break; + + case 'system.theme_set_default': + case 'system.theme_install': + $links_item['link'] = _claro_convert_link_to_action_link($links_item['link'], 'checkmark', 'small'); + break; + } + } + } + } +} + +/** + * Converts a link render element to an action link. + * + * This helper merges every attributes from $link['#attributes'], from + * $link['#options']['attributes'] and from the Url object's. + * + * @param array $link + * Link renderable array. + * @param string|null $icon_name + * The name of the needed icon. When specified, a CSS class will be added with + * the following pattern: 'action-link--icon-[icon_name]'. If the needed icon + * is not implemented in CSS, no icon will be added. + * Currently available icons are: + * - checkmark, + * - cog, + * - ex, + * - plus, + * - trash. + * @param string $size + * Name of the small action link variant. Defaults to 'default'. + * Supported sizes are: + * - default, + * - small, + * - extrasmall. + * @param string $variant + * Variant of the action link. Supported variants are 'default' and 'danger'. + * Defaults to 'default'. + * + * @return array + * The link renderable converted to action link. + */ +function _claro_convert_link_to_action_link(array $link, $icon_name = NULL, $size = 'default', $variant = 'default') { + // Early opt-out if we cannot do anything. + if (empty($link['#type']) || $link['#type'] !== 'link' || empty($link['#url'])) { + return $link; + } + + // \Drupal\Core\Render\Element\Link::preRenderLink adds $link['#attributes'] + // to $link[#options]['attributes'] if it is not empty, but it does not merges + // the 'class' subkey deeply. + // Because of this, when $link[#options]['attributes']['class'] is set, the + // classes defined in $link['#attributes']['class'] are ignored. + // + // To keep this behavior we repeat this for action-link, which means that this + // conversion happens a bit earlier. We unset $link['#attributes'] to prevent + // Link::preRenderLink() doing the same, because for action-links, that would + // be needless. + $link += ['#options' => []]; + if (isset($link['#attributes'])) { + $link['#options'] += [ + 'attributes' => [], + ]; + $link['#options']['attributes'] += $link['#attributes']; + unset($link['#attributes']); + } + $link['#options'] += ['attributes' => []]; + $link['#options']['attributes'] += ['class' => []]; + + // Determine the needed (type) variant. + $variants_supported = ['default', 'danger']; + $variant = is_string($variant) && in_array($variant, $variants_supported) ? $variant : reset($variants_supported); + + // Remove button, button modifier CSS classes and other unwanted ones. + $link['#options']['attributes']['class'] = array_diff($link['#options']['attributes']['class'], [ + 'button', + 'button--action', + 'button--primary', + 'button--danger', + 'button--small', + 'button--extrasmall', + 'link', + ]); + + // Adding the needed CSS classes. + $link['#options']['attributes']['class'][] = 'action-link'; + + // Add the variant-modifier CSS class only if the variant is not the default. + if ($variant !== reset($variants_supported)) { + $link['#options']['attributes']['class'][] = Html::getClass("action-link--$variant"); + } + + // Add the icon modifier CSS class. + if (!empty($icon_name)) { + $link['#options']['attributes']['class'][] = Html::getClass("action-link--icon-$icon_name"); + } + + if ($size && in_array($size, ['small', 'extrasmall'])) { + $link['#options']['attributes']['class'][] = Html::getClass("action-link--$size"); + } + + // If the provided $link is an item of the 'links' theme function, then only + // the attributes of the Url object are processed during rendering. + $url_attributes = $link['#url']->getOption('attributes') ?: []; + $url_attributes = NestedArray::mergeDeep($url_attributes, $link['#options']['attributes']); + $link['#url']->setOption('attributes', $url_attributes); + + return $link; +} + +/** + * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm. + * + * Changes vertical tabs to container. + */ +function claro_form_node_form_alter(&$form, FormStateInterface $form_state) { + $form['#theme'] = ['node_edit_form']; + $form['#attached']['library'][] = 'claro/node-form'; + + $form['advanced']['#type'] = 'container'; + $form['advanced']['#accordion'] = TRUE; + $form['meta']['#type'] = 'container'; + $form['meta']['#access'] = TRUE; + + $form['revision_information']['#type'] = 'container'; + $form['revision_information']['#group'] = 'meta'; + $form['revision_information']['#attributes']['class'][] = 'entity-meta__revision'; +} + +/** + * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\media\MediaForm. + */ +function claro_form_media_form_alter(&$form, FormStateInterface $form_state) { + // Only attach CSS from core if this form comes from Media core, and not from + // the contrib Media Entity 1.x branch. + if (\Drupal::moduleHandler()->moduleExists('media') && $form_state->getFormObject() instanceof MediaForm) { + // @todo Revisit after https://www.drupal.org/node/2892304 is in. It + // introduces a footer region to these forms which will allow for us to + // display a top border over the published checkbox by defining a + // media-edit-form.html.twig template the same way node does. + $form['#attached']['library'][] = 'claro/media-form'; + } +} + +/** + * Implements hook_form_FORM_ID_alter() for view_edit_form. + */ +function claro_form_view_edit_form_alter(&$form, FormStateInterface $form_state) { + // @todo remove this after https://www.drupal.org/node/3051605 has been + // solved. + $form['displays']['top']['tabs']['#prefix'] = preg_replace('/(class="(.+\s)?)tabs(\s.+"|")/', '$1views-tabs$3', $form['displays']['top']['tabs']['#prefix']); + $form['displays']['top']['tabs']['#prefix'] = preg_replace('/(class="(.+\s)?)secondary(\s.+"|")/', '$1views-tabs--secondary$3', $form['displays']['top']['tabs']['#prefix']); + + foreach (Element::children($form['displays']['top']['tabs']) as $tab) { + $form['displays']['top']['tabs'][$tab]['#theme'] = 'menu_local_task__views_ui'; + } + + if (!empty($form['displays'])) { + // Change top extra actions to use the small dropbutton variant. + // @todo Revisit after https://www.drupal.org/node/3057581 is added. + if (!empty($form['displays']['top']['extra_actions'])) { + $form['displays']['top']['extra_actions']['#dropbutton_type'] = 'small'; + } + + // We process the dropbutton-like element on views edit form's + // display settings top section. + // + // That element should be a regular Dropbutton. + // + // After that the reported issue is fixed and the element is rendered with + // the Dropbutton type, we just have to set it's '#dropbutton_type' to + // 'extrasmall'. + // + // @todo: revisit after https://www.drupal.org/node/3057577 is fixed. + $dummy_dropbutton = &$form['displays']['settings']['settings_content']['tab_content']['details']['top']['actions']; + + if ($dummy_dropbutton) { + $child_keys = Element::children($dummy_dropbutton); + $prefix_regex = '/(<.*class\s*= *["\']?)([^"\']*)(.*)/i'; + $child_count = 0; + + foreach ($child_keys as $key) { + if (in_array($key, ['prefix', 'suffix'])) { + continue; + } + $nested_child_keys = Element::children($dummy_dropbutton[$key], TRUE); + + if (!empty($nested_child_keys)) { + foreach ($nested_child_keys as $nested_key) { + $child_count++; + $prefix = $dummy_dropbutton[$key][$nested_key]['#prefix']; + $dummy_dropbutton[$key][$nested_key]['#prefix'] = preg_replace($prefix_regex, '$1$2 dropbutton__item dropbutton__item--extrasmall$3', $prefix); + } + } + else { + $child_count++; + $prefix = $dummy_dropbutton[$key]['#prefix']; + $dummy_dropbutton[$key]['#prefix'] = preg_replace($prefix_regex, '$1$2 dropbutton__item dropbutton__item--extrasmall$3', $prefix); + } + } + + if (!empty($dummy_dropbutton['prefix']) && !empty($dummy_dropbutton['prefix']['#markup'])) { + $classes = 'dropbutton--extrasmall '; + $classes .= ($child_count > 1) ? 'dropbutton--multiple' : 'dropbutton--single'; + $prefix = $dummy_dropbutton['prefix']['#markup']; + $dummy_dropbutton['prefix']['#markup'] = preg_replace($prefix_regex, '$1$2 ' . $classes . '$3', $prefix); + } + } + } +} + +/** + * Implements hook_form_FORM_ID_alter() for views_exposed_form. + */ +function claro_form_views_exposed_form_alter(&$form, FormStateInterface $form_state) { + $view = $form_state->getStorage()['view']; + $view_title = $view->getTitle(); + + // Add BEM classes for items in the form. + // Sorted keys. + $child_keys = Element::children($form, TRUE); + $last_key = NULL; + $child_before_actions_key = NULL; + + foreach ($child_keys as $child_key) { + if (!empty($form[$child_key]['#type'])) { + if ($form[$child_key]['#type'] === 'actions') { + // We need the key of the element that precedes the actions element. + $child_before_actions_key = $last_key; + $form[$child_key]['#attributes']['class'][] = 'views-exposed-form__item'; + $form[$child_key]['#attributes']['class'][] = 'views-exposed-form__item--actions'; + } + + if (!in_array($form[$child_key]['#type'], ['hidden', 'actions'])) { + $form[$child_key]['#wrapper_attributes']['class'][] = 'views-exposed-form__item'; + $last_key = $child_key; + } + } + } + + if ($child_before_actions_key) { + // Add a modifier class to the item that precedes the form actions. + $form[$child_before_actions_key]['#wrapper_attributes']['class'][] = 'views-exposed-form__item--preceding-actions'; + } + + // Add a label so screenreaders can identify the purpose of the exposed form + // without having to scan content that appears further down the page. + $form['#attributes']['aria-label'] = t('Filter the contents of the %view_title view', ['%view_title' => $view_title]); +} + +/** + * Implements hook_preprocess_form_element(). + */ +function claro_preprocess_form_element(&$variables) { + if (!empty($variables['element']['#errors'])) { + $variables['label']['#attributes']['class'][] = 'has-error'; + } + + if ($variables['disabled']) { + $variables['label']['#attributes']['class'][] = 'is-disabled'; + + if (!empty($variables['description']['attributes'])) { + $variables['description']['attributes']->addClass('is-disabled'); + } + } +} + +/** + * Implements template_preprocess_HOOK() for input. + */ +function claro_preprocess_input(&$variables) { + if ( + !empty($variables['element']['#title_display']) && + $variables['element']['#title_display'] === 'attribute' && + !empty((string) $variables['element']['#title']) + ) { + $variables['attributes']['title'] = (string) $variables['element']['#title']; + } + + $type_api = $variables['element']['#type']; + $type_html = $variables['attributes']['type']; + $text_types_html = [ + 'text', + 'email', + 'tel', + 'number', + 'search', + 'password', + 'date', + 'time', + 'file', + 'color', + 'datetime-local', + 'url', + 'month', + 'week', + ]; + + if (in_array($type_html, $text_types_html)) { + $variables['attributes']['class'][] = 'form-element'; + $variables['attributes']['class'][] = Html::getClass('form-element--type-' . $type_html); + $variables['attributes']['class'][] = Html::getClass('form-element--api-' . $type_api); + + if (!empty($variables['element']['#autocomplete_route_name'])) { + $variables['autocomplete_message'] = t('Loading…'); + } + } + + if (in_array($type_html, ['checkbox', 'radio'])) { + $variables['attributes']['class'][] = 'form-boolean'; + $variables['attributes']['class'][] = Html::getClass('form-boolean--type-' . $type_html); + } +} + +/** + * Implements template_preprocess_HOOK() for textarea. + */ +function claro_preprocess_textarea(&$variables) { + $variables['attributes']['class'][] = 'form-element'; + $variables['attributes']['class'][] = 'form-element--type-textarea'; + $variables['attributes']['class'][] = 'form-element--api-textarea'; +} + +/** + * Implements template_preprocess_HOOK() for select. + */ +function claro_preprocess_select(&$variables) { + if (!empty($variables['element']['#title_display']) && $variables['element']['#title_display'] === 'attribute' && !empty((string) $variables['element']['#title'])) { + $variables['attributes']['title'] = (string) $variables['element']['#title']; + } + + $variables['attributes']['class'][] = 'form-element'; + $variables['attributes']['class'][] = $variables['element']['#multiple'] ? + 'form-element--type-select-multiple' : + 'form-element--type-select'; + + if (in_array('block-region-select', $variables['attributes']['class'])) { + $variables['attributes']['class'][] = 'form-element--extrasmall'; + } +} + +/** + * Implements template_preprocess_HOOK() for datetime_wrapper. + */ +function claro_preprocess_datetime_wrapper(&$variables) { + if (!empty($variables['element']['#errors'])) { + $variables['title_attributes']['class'][] = 'has-error'; + } + + if (!empty($variables['element']['#disabled'])) { + $variables['title_attributes']['class'][] = 'is-disabled'; + + if (!empty($variables['description_attributes'])) { + $variables['description_attributes']->addClass('is-disabled'); + } + } +} + +/** + * Implements template_preprocess_HOOK() for fieldset. + */ +function claro_preprocess_fieldset(&$variables) { + $element = $variables['element']; + $composite_types = ['checkboxes', 'radios']; + + if (!empty($element['#type']) && in_array($element['#type'], $composite_types) && !empty($variables['element']['#children_errors'])) { + $variables['legend_span']['attributes']->addClass('has-error'); + } + + if (!empty($element['#disabled'])) { + $variables['legend_span']['attributes']->addClass('is-disabled'); + + if (!empty($variables['description']) && !empty($variables['description']['attributes'])) { + $variables['description']['attributes']->addClass('is-disabled'); + } + } + + // Remove 'container-inline' class from the main attributes and add a flag + // instead. + // @todo remove this after https://www.drupal.org/node/3059593 has been + // resolved. + if (!empty($variables['attributes']['class'])) { + $container_inline_key = array_search('container-inline', $variables['attributes']['class']); + + if ($container_inline_key !== FALSE) { + unset($variables['attributes']['class'][$container_inline_key]); + $variables['inline_items'] = TRUE; + } + } +} + +/** + * Implements hook_preprocess_HOOK() for field_multiple_value_form. + */ +function claro_preprocess_field_multiple_value_form(&$variables) { + // Make disabled available for the template. + $variables['disabled'] = !empty($variables['element']['#disabled']); + + if ($variables['multiple']) { + // Add an additional CSS class for the field label table cell. + // This repeats the logic of template_preprocess_field_multiple_value_form() + // without using '#prefix' and '#suffix' for the wrapper element. + // + // If the field is multiple, we don't have to check the existence of the + // table header cell. + // + // @see template_preprocess_field_multiple_value_form(). + $header_attributes = ['class' => ['form-item__label', 'form-item__label--multiple-value-form']]; + if (!empty($variables['element']['#required'])) { + $header_attributes['class'][] = 'js-form-required'; + $header_attributes['class'][] = 'form-required'; + } + // Using array_key_first() for addressing the first header cell would be + // more elegant here, but we can rely on the related theme.inc preprocess. + $variables['table']['#header'][0]['data'] = [ + '#type' => 'html_tag', + '#tag' => 'h4', + '#value' => $variables['element']['#title'], + '#attributes' => $header_attributes, + ]; + + if ($variables['disabled']) { + $variables['table']['#attributes']['class'][] = 'tabledrag-disabled'; + $variables['table']['#attributes']['class'][] = 'js-tabledrag-disabled'; + + // We will add the 'is-disabled' CSS class to the disabled table header + // cells. + $header_attributes['class'][] = 'is-disabled'; + foreach ($variables['table']['#header'] as &$cell) { + if (is_array($cell) && isset($cell['data'])) { + $cell = $cell + ['class' => []]; + $cell['class'][] = 'is-disabled'; + } + else { + // We have to modify the structure of this header cell. + $cell = [ + 'data' => $cell, + 'class' => ['is-disabled'], + ]; + } + } + } + + // Make add-more button smaller. + if (!empty($variables['button'])) { + $variables['button']['#attributes']['class'][] = 'button--small'; + } + } +} + +/** + * Implements hook_preprocess_HOOK() for form_element__password_confirm. + */ +function claro_preprocess_form_element__password_confirm(&$variables) { + // Add CSS classes needed for theming the password confirm widget. + $variables['attributes']['class'][] = 'password-confirm'; + $variables['attributes']['class'][] = 'is-initial'; + $variables['attributes']['class'][] = 'is-password-empty'; + $variables['attributes']['class'][] = 'is-confirm-empty'; +} + +/** + * Implements hook_preprocess_HOOK() for form_element__password. + */ +function claro_preprocess_form_element__password(&$variables) { + if (!empty($variables['element']['#array_parents']) && in_array('pass1', $variables['element']['#array_parents'])) { + // This is the main password form element. + $variables['attributes']['class'][] = 'password-confirm__password'; + } + + if (!empty($variables['element']['#array_parents']) && in_array('pass2', $variables['element']['#array_parents'])) { + // This is the password confirm form element. + $variables['attributes']['class'][] = 'password-confirm__confirm'; + } +} + +/** + * Implements template_preprocess_HOOK() for checkboxes. + */ +function claro_preprocess_checkboxes(&$variables) { + $variables['attributes']['class'][] = 'form-boolean-group'; +} + +/** + * Implements template_preprocess_HOOK() for radios. + */ +function claro_preprocess_radios(&$variables) { + $variables['attributes']['class'][] = 'form-boolean-group'; +} + +/** + * Implements template_preprocess_HOOK() for filter_tips. + */ +function claro_preprocess_filter_tips(&$variables) { + $variables['#attached']['library'][] = 'filter/drupal.filter'; +} + +/** + * Implements template_preprocess_HOOK() for table. + */ +function claro_preprocess_table(&$variables) { + // Adding table sort indicator CSS class for inactive sort link. + // @todo Revisit after https://www.drupal.org/node/3025726 or + // https://www.drupal.org/node/1973418 is in. + if (!empty($variables['header'])) { + foreach ($variables['header'] as &$header_cell) { + // For 8.6.x and below. + // @todo Remove this after 8.6.x is out of support. + if ($header_cell['content'] instanceof GeneratedLink) { + $dom_doc = Html::load($header_cell['content']->getGeneratedLink()); + $anchors = $dom_doc->getElementsByTagName('a'); + + if (!empty($anchors)) { + foreach ($anchors as $anchor) { + $anchor_href = $anchor->getAttribute('href'); + $parsed_url = UrlHelper::parse($anchor_href); + $query = !empty($parsed_url['query']) ? $parsed_url['query'] : []; + + if (isset($query['order']) && isset($query['sort'])) { + $header_cell['attributes']->addClass('sortable-heading'); + } + } + } + } + + // For 8.7.x and above. + if ($header_cell['content'] instanceof Link) { + $query = $header_cell['content']->getUrl()->getOption('query') ?: []; + + if (isset($query['order']) && isset($query['sort'])) { + $header_cell['attributes']->addClass('sortable-heading'); + } + } + } + } + + // Mark the whole table and the first cells if rows are draggable. + if (!empty($variables['rows'])) { + $draggable_row_found = FALSE; + foreach ($variables['rows'] as &$row) { + /** @var \Drupal\Core\Template\Attribute $row['attributes'] */ + if (!empty($row['attributes']) && $row['attributes']->hasClass('draggable')) { + if (!$draggable_row_found) { + $variables['attributes']['class'][] = 'draggable-table'; + $draggable_row_found = TRUE; + } + + reset($row['cells']); + $first_cell_key = key($row['cells']); + // The 'attributes' key is always here and it is an + // \Drupal\Core\Template\Attribute. + // @see template_preprocess_table(); + $row['cells'][$first_cell_key]['attributes']->addClass('tabledrag-cell'); + + // Check that the first cell is empty or not. + if (empty($row['cells'][$first_cell_key]) || empty($row['cells'][$first_cell_key]['content'])) { + $row['cells'][$first_cell_key]['attributes']->addClass('tabledrag-cell--only-drag'); + } + } + } + } +} + +/** + * Implements template_preprocess_HOOK() for field_ui_table. + */ +function claro_preprocess_field_ui_table(&$variables) { + claro_preprocess_table($variables); +} + +/** + * Implements template_preprocess_HOOK() for views_view_table. + * + * @todo Revisit after https://www.drupal.org/node/3025726 or + * https://www.drupal.org/node/1973418 is in. + */ +function claro_preprocess_views_view_table(&$variables) { + if (!empty($variables['header'])) { + foreach ($variables['header'] as &$header_cell) { + if (!empty($header_cell['url'])) { + $parsed_url = UrlHelper::parse($header_cell['url']); + $query = !empty($parsed_url['query']) ? $parsed_url['query'] : []; + + if (isset($query['order']) && isset($query['sort'])) { + $header_cell['attributes']->addClass('sortable-heading'); + } + } + } + } +} + +/** + * Implements hook_preprocess_HOOK() for links__dropbutton__operations. + * + * Operations always use the extra small dropbutton variant. + */ +function claro_preprocess_links__dropbutton__operations(&$variables) { + $item_classes = ['dropbutton__item', 'dropbutton__item--extrasmall']; + $variables['attributes']['class'][] = 'dropbutton--extrasmall'; + + foreach ($variables['links'] as &$link_data) { + $link_data['attributes']->addClass($item_classes); + } +} + +/** + * Implements hook_preprocess_HOOK() for links__dropbutton. + */ +function claro_preprocess_links__dropbutton(&$variables) { + // Add the right CSS class for the dropbutton list that helps reducing FOUC. + if (!empty($variables['links'])) { + $variables['attributes']['class'][] = count($variables['links']) > 1 + ? 'dropbutton--multiple' + : 'dropbutton--single'; + } + + $item_classes = ['dropbutton__item']; + + // Check that the dropbutton has a supported variant class. + // @todo Revisit after https://www.drupal.org/node/3057581 is added. + if (!empty($variables['attributes']['class'])) { + if (array_search('dropbutton--small', $variables['attributes']['class'])) { + $item_classes[] = 'dropbutton__item--small'; + } + elseif (array_search('dropbutton--extrasmall', $variables['attributes']['class'])) { + $item_classes[] = 'dropbutton__item--extrasmall'; + } + } + + foreach ($variables['links'] as &$link_data) { + $link_data['attributes']->addClass($item_classes); + } +} + +/** + * Implements hook_preprocess_HOOK() for views_ui_display_tab_bucket. + */ +function claro_preprocess_views_ui_display_tab_bucket(&$variables) { + // Instead of re-styling Views UI dropbuttons with module-specific CSS styles, + // change dropbutton variants to the extra small version. + // @todo Revisit after https://www.drupal.org/node/3057581 is added. + if (!empty($variables['actions']) && $variables['actions']['#type'] === 'dropbutton') { + $variables['actions']['#dropbutton_type'] = 'extrasmall'; + } +} + +/** + * Implements hook_preprocess_HOOK() for status_messages. + */ +function claro_preprocess_status_messages(&$variables) { + $variables['title_ids'] = []; + foreach ($variables['message_list'] as $message_type => $messages) { + $variables['title_ids'][$message_type] = Html::getUniqueId("message-$message_type-title"); + } +} + +/** + * Implements hook_preprocess_HOOK() for system_themes_page. + */ +function claro_preprocess_system_themes_page(&$variables) { + if (!empty($variables['theme_groups'])) { + foreach ($variables['theme_groups'] as &$theme_group) { + if (!empty($theme_group['themes'])) { + foreach ($theme_group['themes'] as &$theme_card) { + $theme_card['title_id'] = Html::getUniqueId($theme_card['name'] . '-label'); + $description_is_empty = empty((string) $theme_card['description']); + + // Set description_id only if the description is not empty. + if (!$description_is_empty) { + $theme_card['description_id'] = Html::getUniqueId($theme_card['name'] . '-description'); + } + + if (!empty($theme_card['operations']) && !empty($theme_card['operations']['#theme']) && $theme_card['operations']['#theme'] === 'links') { + $theme_card['operations']['#theme'] = 'links__action_links'; + } + } + } + } + } +} + +/** + * Implements hook_preprocess_HOOK() for links__action_links. + */ +function claro_preprocess_links__action_links(&$variables) { + $variables['attributes']['class'][] = 'action-links'; + foreach ($variables['links'] as $delta => $link_item) { + $variables['links'][$delta]['attributes']->addClass('action-links__item'); + } +} + +/** + * Implements hook_preprocess_HOOK() for file_managed_file. + */ +function claro_preprocess_file_managed_file(&$variables) { + $element = $variables['element']; + + // Calculate helper values for the template. + $upload_is_accessible = !isset($element['upload']['#access']) || $element['upload']['#access'] !== FALSE; + $is_multiple = !empty($element['#cardinality']) && $element['#cardinality'] !== 1; + $has_value = isset($element['#value']['fids']) && !empty($element['#value']['fids']); + $display_can_be_displayed = !empty($element['#display_field']); + // Display is rendered in a separate table cell for multiple value widgets. + $display_is_visible = $display_can_be_displayed && !$is_multiple && isset($element['display']['#type']) && $element['display']['#type'] !== 'hidden'; + $description_can_be_displayed = !empty($element['#description_field']); + $description_is_visible = $description_can_be_displayed && isset($element['description']); + + $variables['multiple'] = $is_multiple; + $variables['upload'] = $upload_is_accessible; + $variables['has_value'] = $has_value; + $variables['has_meta'] = $display_is_visible || $description_is_visible; + $variables['display'] = $display_is_visible; +} + +/** + * Implements hook_preprocess_HOOK() for file_widget_multiple. + */ +function claro_preprocess_file_widget_multiple(&$variables) { + $has_upload = FALSE; + + if (isset($variables['table']['#type']) && $variables['table']['#type'] === 'table') { + // Add a variant class for the table. + $variables['table']['#attributes']['class'][] = 'table-file-multiple-widget'; + + // Mark table disabled if the field widget is disabled. + if (isset($variables['element']['#disabled']) && $variables['element']['#disabled']) { + $variables['table']['#attributes']['class'][] = 'tabledrag-disabled'; + $variables['table']['#attributes']['class'][] = 'js-tabledrag-disabled'; + + // We will add the 'is-disabled' CSS class to the disabled table header + // cells. + foreach ($variables['table']['#header'] as &$cell) { + if (is_array($cell) && isset($cell['data'])) { + $cell = $cell + ['class' => []]; + $cell['class'][] = 'is-disabled'; + } + else { + // We have to modify the structure of this header cell. + $cell = [ + 'data' => $cell, + 'class' => ['is-disabled'], + ]; + } + } + } + + // Mark operations column cells with a CSS class. + if (isset($variables['table']['#rows']) && is_array($variables['table']['#rows'])) { + foreach ($variables['table']['#rows'] as $row_key => $row) { + if (isset($row['data']) && is_array($row['data'])) { + $last_cell = end($row['data']); + $last_cell_key = key($row['data']); + + if (is_array($last_cell['data'])) { + foreach ($last_cell['data'] as $last_cell_item) { + if (isset($last_cell_item['#attributes']['class']) && is_array($last_cell_item['#attributes']['class']) && in_array('remove-button', $last_cell_item['#attributes']['class'])) { + $variables['table']['#rows'][$row_key]['data'][$last_cell_key] += ['class' => []]; + $variables['table']['#rows'][$row_key]['data'][$last_cell_key]['class'][] = 'file-operations-cell'; + break 1; + } + } + } + } + } + } + + // Add a CSS class to the table if an upload widget is present. + // This is required for removing the border of the last table row. + if (!empty($variables['element'])) { + $element_keys = Element::children($variables['element']); + + foreach ($element_keys as $delta) { + if (!isset($variables['element'][$delta]['upload']['#access']) || $variables['element'][$delta]['upload']['#access'] !== FALSE) { + $has_upload = TRUE; + break 1; + } + } + } + $variables['table']['#attributes']['class'][] = $has_upload ? 'table-file-multiple-widget--has-upload' : 'table-file-multiple-widget--no-upload'; + } + + $table_is_not_empty = isset($variables['table']['#rows']) && !empty($variables['table']['#rows']); + $table_is_accessible = !isset($variables['table']['#access']) || (isset($variables['table']['#access']) && $variables['table']['#access'] !== FALSE); + $variables['has_table'] = $table_is_not_empty && $table_is_accessible; +} + +/** + * Implements hook_preprocess_HOOK() for image_widget. + */ +function claro_preprocess_image_widget(&$variables) { + $element = $variables['element']; + + // Stable adds the file size as #suffix for image file_link renderable array. + // We have to remove that because we will render it in our file_link template + // for every kind of files, and not just for images. + if (!empty($variables['element']['fids']['#value'])) { + $file = reset($variables['element']['#files']); + unset($variables['data']['file_' . $file->id()]['filename']['#suffix']); + } + + // Calculate helper values for the template. + $upload_is_accessible = !isset($element['upload']['#access']) || $element['upload']['#access'] !== FALSE; + $is_multiple = !empty($element['#cardinality']) && $element['#cardinality'] !== 1; + $has_value = isset($element['#value']['fids']) && !empty($element['#value']['fids']); + $alt_can_be_displayed = !empty($element['#alt_field']); + $alt_is_visible = $alt_can_be_displayed && (!isset($element['alt']['#access']) || $element['alt']['#access'] !== FALSE); + $title_can_be_displayed = !empty($element['#title_field']); + $title_is_visible = $title_can_be_displayed && (!isset($element['title']['#access']) || $element['title']['#access'] !== FALSE); + + $variables['multiple'] = $is_multiple; + $variables['upload'] = $upload_is_accessible; + $variables['has_value'] = $has_value; + $variables['has_meta'] = $alt_is_visible || $title_is_visible; +} diff --git a/core/themes/claro/config/optional/block.block.claro_breadcrumbs.yml b/core/themes/claro/config/optional/block.block.claro_breadcrumbs.yml new file mode 100644 index 0000000000..5133f919e2 --- /dev/null +++ b/core/themes/claro/config/optional/block.block.claro_breadcrumbs.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - system + theme: + - claro +id: claro_breadcrumbs +theme: claro +region: breadcrumb +weight: 0 +provider: null +plugin: system_breadcrumb_block +settings: + id: system_breadcrumb_block + label: Breadcrumbs + provider: system + label_display: '0' +visibility: { } diff --git a/core/themes/claro/config/optional/block.block.claro_content.yml b/core/themes/claro/config/optional/block.block.claro_content.yml new file mode 100644 index 0000000000..ae811dba69 --- /dev/null +++ b/core/themes/claro/config/optional/block.block.claro_content.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - system + theme: + - claro +id: claro_content +theme: claro +region: content +weight: 0 +provider: null +plugin: system_main_block +settings: + id: system_main_block + label: 'Main page content' + provider: system + label_display: '0' +visibility: { } diff --git a/core/themes/claro/config/optional/block.block.claro_help.yml b/core/themes/claro/config/optional/block.block.claro_help.yml new file mode 100644 index 0000000000..3a0ad990e5 --- /dev/null +++ b/core/themes/claro/config/optional/block.block.claro_help.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - help + theme: + - claro +id: claro_help +theme: claro +region: help +weight: 0 +provider: null +plugin: help_block +settings: + id: help_block + label: Help + provider: help + label_display: '0' +visibility: { } diff --git a/core/themes/claro/config/optional/block.block.claro_local_actions.yml b/core/themes/claro/config/optional/block.block.claro_local_actions.yml new file mode 100644 index 0000000000..7e2a4e1d53 --- /dev/null +++ b/core/themes/claro/config/optional/block.block.claro_local_actions.yml @@ -0,0 +1,17 @@ +langcode: en +status: true +dependencies: + theme: + - claro +id: claro_local_actions +theme: claro +region: content +weight: -10 +provider: null +plugin: local_actions_block +settings: + id: local_actions_block + label: 'Primary admin actions' + provider: core + label_display: '0' +visibility: { } diff --git a/core/themes/claro/config/optional/block.block.claro_messages.yml b/core/themes/claro/config/optional/block.block.claro_messages.yml new file mode 100644 index 0000000000..d6874e5605 --- /dev/null +++ b/core/themes/claro/config/optional/block.block.claro_messages.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + module: + - system + theme: + - claro +id: claro_messages +theme: claro +region: highlighted +weight: 0 +provider: null +plugin: system_messages_block +settings: + id: system_messages_block + label: 'Status messages' + provider: system + label_display: '0' +visibility: { } diff --git a/core/themes/claro/config/optional/block.block.claro_page_title.yml b/core/themes/claro/config/optional/block.block.claro_page_title.yml new file mode 100644 index 0000000000..bf7268d595 --- /dev/null +++ b/core/themes/claro/config/optional/block.block.claro_page_title.yml @@ -0,0 +1,17 @@ +langcode: en +status: true +dependencies: + theme: + - claro +id: claro_page_title +theme: claro +region: header +weight: -30 +provider: null +plugin: page_title_block +settings: + id: page_title_block + label: 'Page title' + provider: core + label_display: '0' +visibility: { } diff --git a/core/themes/claro/config/optional/block.block.claro_primary_local_tasks.yml b/core/themes/claro/config/optional/block.block.claro_primary_local_tasks.yml new file mode 100644 index 0000000000..51af4c8e5c --- /dev/null +++ b/core/themes/claro/config/optional/block.block.claro_primary_local_tasks.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + theme: + - claro +id: claro_primary_local_tasks +theme: claro +region: header +weight: 0 +provider: null +plugin: local_tasks_block +settings: + id: local_tasks_block + label: 'Primary tabs' + provider: core + label_display: '0' + primary: true + secondary: false +visibility: { } diff --git a/core/themes/claro/config/optional/block.block.claro_secondary_local_tasks.yml b/core/themes/claro/config/optional/block.block.claro_secondary_local_tasks.yml new file mode 100644 index 0000000000..e26e326b9c --- /dev/null +++ b/core/themes/claro/config/optional/block.block.claro_secondary_local_tasks.yml @@ -0,0 +1,19 @@ +langcode: en +status: true +dependencies: + theme: + - claro +id: claro_secondary_local_tasks +theme: claro +region: pre_content +weight: 0 +provider: null +plugin: local_tasks_block +settings: + id: local_tasks_block + label: 'Secondary tabs' + provider: core + label_display: '0' + primary: false + secondary: true +visibility: { } diff --git a/core/themes/claro/config/schema/claro.schema.yml b/core/themes/claro/config/schema/claro.schema.yml new file mode 100644 index 0000000000..171129d5cb --- /dev/null +++ b/core/themes/claro/config/schema/claro.schema.yml @@ -0,0 +1,5 @@ +# Schema for the configuration files of the Claro theme. + +claro.settings: + type: theme_settings + label: 'Claro settings' diff --git a/core/themes/claro/css/src/base/elements.css b/core/themes/claro/css/src/base/elements.css new file mode 100644 index 0000000000..0b3aeb1fc5 --- /dev/null +++ b/core/themes/claro/css/src/base/elements.css @@ -0,0 +1,316 @@ +/* + * DO NOT EDIT THIS FILE. + * See the following change record for more information, + * https://www.drupal.org/node/2815083 + * @preserve + */ + +/** + * Generic elements. + */ + +:root { + /* + * Color Palette. + */ + /* Secondary. */ + /* Variations. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 10% darker than base. */ /* 20% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ + /* + * Base. + */ + /* + * Typography. + */ /* 1rem = 16px if font root is 100% ands browser defaults are used. */ /* ~32px */ /* ~29px */ /* ~26px */ /* ~23px */ /* ~20px */ /* 18px */ /* ~14px */ /* ~13px */ /* ~11px */ + /** + * Spaces. + */ /* 3 * 16px = 48px */ /* 1.5 * 16px = 24px */ /* 1 * 16px = 16px */ /* 0.75 * 16px = 12px */ /* 0.5 * 16px = 8px */ + /* + * Common. + */ + /* + * Inputs. + */ /* Absolute zero with opacity. */ /* Davy's grey with 0.6 opacity. */ /* Light gray with 0.3 opacity on white bg. */ /* Old silver with 0.5 opacity on white bg. */ /* (1/8)em ~ 2px */ /* (1/16)em ~ 1px */ /* Font size is too big to use 1rem for extrasmall line-height */ /* 7px inside the form element label. */ /* 8px with the checkbox width of 19px */ + /* + * Details. + */ + /** + * Buttons. + */ + /** + * jQuery.UI dropdown. + */ /* Light gray with 0.8 opacity. */ /* Text color with 0.1 opacity. */ + /** + * Progress bar. + */ + /** + * Tabledrag icon size. + */ /* 17px */ + /** + * Ajax progress. + */ + /** + * Breadcrumb. + */ +} + +html { + font-family: BlinkMacSystemFont +, +-apple-system +, +"Segoe UI" +, +Roboto +, +Oxygen-Sans +, +Ubuntu +, +Cantarell +, +"Helvetica Neue" +, +sans-serif; + font-size: 100%; + font-weight: normal; + font-style: normal; + line-height: 1.5; +} + +body { + color: #222330; + background: #fff; +} + +a, +.link { + color: #003cc5; +} + +a:hover, +a:focus, +.link:hover, +.link:focus { + text-decoration: none; + outline: 0; +} + +a:hover, +.link:hover { + color: #0036b1; +} + +a:active, +.link:active { + color: #00339a; +} + +hr { + height: 1px; + margin: 1rem 0; + padding: 0; + border: none; + background: rgba(142, 146, 156, 0.5); +} + +summary { + font-weight: bold; +} + +/** + * Reusable heading classes are included to help modules change the styling of + * headings on a page without affecting accessibility. + */ + +h1, +.heading-a { + margin: 1rem 0 0.75rem; + font-size: 2.027rem; + font-weight: bold; + line-height: 1.3; +} + +h2, +.heading-b { + margin: 1rem 0 0.75rem; + font-size: 1.802rem; + font-weight: bold; + line-height: 1.3; +} + +h3, +.heading-c { + margin: 1rem 0 0.75rem; + font-size: 1.602rem; + font-weight: bold; + line-height: 1.3; +} + +h4, +.heading-d { + margin: 1rem 0 0.75rem; + font-size: 1.424rem; + font-weight: bold; + line-height: 1.3; +} + +h5, +.heading-e { + margin: 1rem 0 0.75rem; + font-size: 1.266rem; + font-weight: bold; + line-height: 1.3; +} + +h6, +.heading-f { + margin: 1rem 0 0.75rem; + font-size: 1.125rem; + font-weight: bold; + line-height: 1.3; +} + +p { + margin: 1em 0; +} + +dl { + margin: 0 0 20px; +} + +dl dd, +dl dl { + margin-bottom: 10px; + margin-left: 20px; /* LTR */ +} + +[dir="rtl"] dl dd, +[dir="rtl"] dl dl { + margin-right: 20px; +} + +blockquote { + margin: 1em 40px; +} + +address { + font-style: italic; +} + +u, +ins { + text-decoration: none; +} + +s, +strike, +del { + text-decoration: line-through; +} + +big { + font-size: larger; +} + +small { + font-size: smaller; +} + +sub { + vertical-align: sub; + font-size: smaller; + line-height: normal; +} + +sup { + vertical-align: super; + font-size: smaller; + line-height: normal; +} + +abbr, +acronym { + border-bottom: dotted 1px; +} + +ul { + margin: 0.25em 0 0.25em 1.5em; /* LTR */ + padding-left: 0; + list-style-type: disc; + list-style-image: none; +} + +[dir="rtl"] ul { + margin-right: 1.5em; + margin-left: 0; + padding-right: 0; +} + +/* This is required to win over specificity of [dir="rtl"] ul */ + +[dir="rtl"] .messages__list { + margin-right: 0; +} + +ol { + margin: 0.25em 0 0.25em 2em; /* LTR */ + padding: 0; + list-style-type: decimal; +} + +[dir="rtl"] ol { + margin-right: 2em; + margin-left: 0; +} + +/** + * Fix duplicate border caused by normalize.css adding border-bottom without + * removing the text-decoration. + */ + +abbr[title] { + text-decoration: none; +} + +code { + margin: 0.5em 0; +} + +pre { + margin: 0.5em 0; + white-space: pre-wrap; +} + +details { + line-height: 1.295em; +} + +details summary { + padding: 0.95em 1.45em; +} + +details summary:focus { + outline: none; +} + +img { + max-width: 100%; + height: auto; +} + +/** + * Default focus styles for focused elements. + * + * This is applied globally to all interactive elements except Toolbar and + * Settings Tray since they have their own styles. + * + * @todo https://www.drupal.org/project/claro/issues/3048785 :focus selector is + * active for non-interactive elements in Internet Explorer 11. + */ + +.page-wrapper *:focus, +.ui-dialog *:focus { + outline: 2px dotted transparent; + -webkit-box-shadow: 0 0 0 2px #fff, 0 0 0 5px #26a769; + box-shadow: 0 0 0 2px #fff, 0 0 0 5px #26a769; +} diff --git a/core/themes/claro/css/src/base/elements.pcss.css b/core/themes/claro/css/src/base/elements.pcss.css new file mode 100644 index 0000000000..8df7ed0f37 --- /dev/null +++ b/core/themes/claro/css/src/base/elements.pcss.css @@ -0,0 +1,213 @@ +/** + * Generic elements. + */ + +@import "./variables.pcss.css"; + +html { + font-family: var(--font-family); + font-size: 100%; + font-weight: normal; + font-style: normal; + line-height: var(--line-height); +} +body { + color: var(--color-fg); + background: var(--color-bg); +} + +a, +.link { + color: var(--color-link); +} +a:hover, +a:focus, +.link:hover, +.link:focus { + text-decoration: none; + outline: 0; +} +a:hover, +.link:hover { + color: var(--color-link-hover); +} +a:active, +.link:active { + color: var(--color-link-active); +} +hr { + height: 1px; + margin: var(--space-m) 0; + padding: 0; + border: none; + background: var(--color-divider); +} + +summary { + font-weight: bold; +} + +/** + * Reusable heading classes are included to help modules change the styling of + * headings on a page without affecting accessibility. + */ +h1, +.heading-a { + margin: var(--space-m) 0 var(--space-s); + font-size: var(--font-size-h1); + font-weight: bold; + line-height: var(--line-height-heading); +} +h2, +.heading-b { + margin: var(--space-m) 0 var(--space-s); + font-size: var(--font-size-h2); + font-weight: bold; + line-height: var(--line-height-heading); +} +h3, +.heading-c { + margin: var(--space-m) 0 var(--space-s); + font-size: var(--font-size-h3); + font-weight: bold; + line-height: var(--line-height-heading); +} +h4, +.heading-d { + margin: var(--space-m) 0 var(--space-s); + font-size: var(--font-size-h4); + font-weight: bold; + line-height: var(--line-height-heading); +} +h5, +.heading-e { + margin: var(--space-m) 0 var(--space-s); + font-size: var(--font-size-h5); + font-weight: bold; + line-height: var(--line-height-heading); +} +h6, +.heading-f { + margin: var(--space-m) 0 var(--space-s); + font-size: var(--font-size-h6); + font-weight: bold; + line-height: var(--line-height-heading); +} +p { + margin: 1em 0; +} +dl { + margin: 0 0 20px; +} +dl dd, +dl dl { + margin-bottom: 10px; + margin-left: 20px; /* LTR */ +} +[dir="rtl"] dl dd, +[dir="rtl"] dl dl { + margin-right: 20px; +} +blockquote { + margin: 1em 40px; +} +address { + font-style: italic; +} +u, +ins { + text-decoration: none; +} +s, +strike, +del { + text-decoration: line-through; +} +big { + font-size: larger; +} +small { + font-size: smaller; +} +sub { + vertical-align: sub; + font-size: smaller; + line-height: normal; +} +sup { + vertical-align: super; + font-size: smaller; + line-height: normal; +} +abbr, +acronym { + border-bottom: dotted 1px; +} +ul { + margin: 0.25em 0 0.25em 1.5em; /* LTR */ + padding-left: 0; + list-style-type: disc; + list-style-image: none; +} +[dir="rtl"] ul { + margin-right: 1.5em; + margin-left: 0; + padding-right: 0; +} +/* This is required to win over specificity of [dir="rtl"] ul */ +[dir="rtl"] .messages__list { + margin-right: 0; +} +ol { + margin: 0.25em 0 0.25em 2em; /* LTR */ + padding: 0; + list-style-type: decimal; +} +[dir="rtl"] ol { + margin-right: 2em; + margin-left: 0; +} + +/** + * Fix duplicate border caused by normalize.css adding border-bottom without + * removing the text-decoration. + */ +abbr[title] { + text-decoration: none; +} + +code { + margin: 0.5em 0; +} +pre { + margin: 0.5em 0; + white-space: pre-wrap; +} +details { + line-height: 1.295em; +} +details summary { + padding: 0.95em 1.45em; +} +details summary:focus { + outline: none; +} +img { + max-width: 100%; + height: auto; +} + +/** + * Default focus styles for focused elements. + * + * This is applied globally to all interactive elements except Toolbar and + * Settings Tray since they have their own styles. + * + * @todo https://www.drupal.org/project/claro/issues/3048785 :focus selector is + * active for non-interactive elements in Internet Explorer 11. + */ +.page-wrapper *:focus, +.ui-dialog *:focus { + outline: 2px dotted transparent; + box-shadow: 0 0 0 2px var(--color-white), 0 0 0 5px var(--color-focus); +} diff --git a/core/themes/claro/css/src/base/print.css b/core/themes/claro/css/src/base/print.css new file mode 100644 index 0000000000..730acd1f82 --- /dev/null +++ b/core/themes/claro/css/src/base/print.css @@ -0,0 +1,89 @@ +/* + * DO NOT EDIT THIS FILE. + * See the following change record for more information, + * https://www.drupal.org/node/2815083 + * @preserve + */ +@media print { + * { + color: #000 !important; /* Black prints faster: h5bp.com/s */ + background-color: transparent !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + text-shadow: none !important; + } + body { + padding-top: 0; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; /* h5bp.com/t */ + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + a, + .link { + color: #000; + } + .button, + .button--primary { + background: none !important; + } + .messages { + border-width: 1px; + border-color: #999; + } + .is-collapse-enabled .tabs { + max-height: 999em; + } + .is-horizontal .tabs__tab { + margin: 0 4px !important; + border-radius: 4px 4px 0 0 !important; + } + .dropbutton-multiple .dropbutton .secondary-action { + display: block; + } + .js .dropbutton-widget, + .js td .dropbutton-widget /* Splitbuttons */ { + position: relative; + } + .js .dropbutton .dropbutton-toggle { + display: none; + } + .js .dropbutton-multiple .dropbutton-widget { + border-radius: 4px; + background: none; + } + input.form-autocomplete, + input.form-text, + input.form-tel, + input.form-email, + input.form-url, + input.form-search, + input.form-number, + input.form-color, + input.form-file, + textarea.form-textarea, + select.form-select { + border-width: 1px; + } +} diff --git a/core/themes/claro/css/src/base/print.pcss.css b/core/themes/claro/css/src/base/print.pcss.css new file mode 100644 index 0000000000..16987c4c8e --- /dev/null +++ b/core/themes/claro/css/src/base/print.pcss.css @@ -0,0 +1,82 @@ +@media print { + * { + color: #000 !important; /* Black prints faster: h5bp.com/s */ + background-color: transparent !important; + box-shadow: none !important; + text-shadow: none !important; + } + body { + padding-top: 0; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; /* h5bp.com/t */ + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + a, + .link { + color: #000; + } + .button, + .button--primary { + background: none !important; + } + .messages { + border-width: 1px; + border-color: #999; + } + .is-collapse-enabled .tabs { + max-height: 999em; + } + .is-horizontal .tabs__tab { + margin: 0 4px !important; + border-radius: 4px 4px 0 0 !important; + } + .dropbutton-multiple .dropbutton .secondary-action { + display: block; + } + .js .dropbutton-widget, + .js td .dropbutton-widget /* Splitbuttons */ { + position: relative; + } + .js .dropbutton .dropbutton-toggle { + display: none; + } + .js .dropbutton-multiple .dropbutton-widget { + border-radius: 4px; + background: none; + } + input.form-autocomplete, + input.form-text, + input.form-tel, + input.form-email, + input.form-url, + input.form-search, + input.form-number, + input.form-color, + input.form-file, + textarea.form-textarea, + select.form-select { + border-width: 1px; + } +} diff --git a/core/themes/claro/css/src/base/typography.css b/core/themes/claro/css/src/base/typography.css new file mode 100644 index 0000000000..fec3341074 --- /dev/null +++ b/core/themes/claro/css/src/base/typography.css @@ -0,0 +1,42 @@ +/* + * DO NOT EDIT THIS FILE. + * See the following change record for more information, + * https://www.drupal.org/node/2815083 + * @preserve + */ +/** +* Reusable utility classes that apply vertical spacing consistency and inline +* with the base line height of Claro. +*/ +.leader { + margin-top: 20px; + margin-top: 1.538rem; +} +.leader-double { + margin-top: 40px; + margin-top: 3.076rem; +} +.leader-triple { + margin-top: 60px; + margin-top: 4.614rem; +} +.leader-quadruple { + margin-top: 80px; + margin-top: 6.152rem; +} +.trailer { + margin-bottom: 20px; + margin-bottom: 1.538rem; +} +.trailer-double { + margin-bottom: 40px; + margin-bottom: 3.076rem; +} +.trailer-triple { + margin-bottom: 60px; + margin-bottom: 4.614rem; +} +.trailer-quadruple { + margin-bottom: 80px; + margin-bottom: 6.152rem; +} diff --git a/core/themes/claro/css/src/base/typography.pcss.css b/core/themes/claro/css/src/base/typography.pcss.css new file mode 100644 index 0000000000..6004d69c68 --- /dev/null +++ b/core/themes/claro/css/src/base/typography.pcss.css @@ -0,0 +1,36 @@ +/** +* Reusable utility classes that apply vertical spacing consistency and inline +* with the base line height of Claro. +*/ +.leader { + margin-top: 20px; + margin-top: 1.538rem; +} +.leader-double { + margin-top: 40px; + margin-top: 3.076rem; +} +.leader-triple { + margin-top: 60px; + margin-top: 4.614rem; +} +.leader-quadruple { + margin-top: 80px; + margin-top: 6.152rem; +} +.trailer { + margin-bottom: 20px; + margin-bottom: 1.538rem; +} +.trailer-double { + margin-bottom: 40px; + margin-bottom: 3.076rem; +} +.trailer-triple { + margin-bottom: 60px; + margin-bottom: 4.614rem; +} +.trailer-quadruple { + margin-bottom: 80px; + margin-bottom: 6.152rem; +} diff --git a/core/themes/claro/css/src/base/variables.css b/core/themes/claro/css/src/base/variables.css new file mode 100644 index 0000000000..aa4737c042 --- /dev/null +++ b/core/themes/claro/css/src/base/variables.css @@ -0,0 +1,49 @@ +/* + * DO NOT EDIT THIS FILE. + * See the following change record for more information, + * https://www.drupal.org/node/2815083 + * @preserve + */ +:root { + /* + * Color Palette. + */ + /* Secondary. */ + /* Variations. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 10% darker than base. */ /* 20% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ + /* + * Base. + */ + /* + * Typography. + */ /* 1rem = 16px if font root is 100% ands browser defaults are used. */ /* ~32px */ /* ~29px */ /* ~26px */ /* ~23px */ /* ~20px */ /* 18px */ /* ~14px */ /* ~13px */ /* ~11px */ + /** + * Spaces. + */ /* 3 * 16px = 48px */ /* 1.5 * 16px = 24px */ /* 1 * 16px = 16px */ /* 0.75 * 16px = 12px */ /* 0.5 * 16px = 8px */ + /* + * Common. + */ + /* + * Inputs. + */ /* Absolute zero with opacity. */ /* Davy's grey with 0.6 opacity. */ /* Light gray with 0.3 opacity on white bg. */ /* Old silver with 0.5 opacity on white bg. */ /* (1/8)em ~ 2px */ /* (1/16)em ~ 1px */ /* Font size is too big to use 1rem for extrasmall line-height */ /* 7px inside the form element label. */ /* 8px with the checkbox width of 19px */ + /* + * Details. + */ + /** + * Buttons. + */ + /** + * jQuery.UI dropdown. + */ /* Light gray with 0.8 opacity. */ /* Text color with 0.1 opacity. */ + /** + * Progress bar. + */ + /** + * Tabledrag icon size. + */ /* 17px */ + /** + * Ajax progress. + */ + /** + * Breadcrumb. + */ +} diff --git a/core/themes/claro/css/src/base/variables.pcss.css b/core/themes/claro/css/src/base/variables.pcss.css new file mode 100644 index 0000000000..dc855b4cc6 --- /dev/null +++ b/core/themes/claro/css/src/base/variables.pcss.css @@ -0,0 +1,180 @@ +:root { + /* + * Color Palette. + */ + --color-absolutezero: #003cc5; + --color-white: #fff; + --color-text: #222330; + --color-text-light: var(--color-grayblue); + --color-whitesmoke: #f3f4f9; + --color-whitesmoke-light: #fafbfd; + --color-whitesmoke-o-40: rgba(243, 244, 249, 0.4); + /* Secondary. */ + --color-lightgray: #d4d4d8; + --color-lightgray-o-80: rgba(212, 212, 218, 0.8); + --color-grayblue: #8e929c; + --color-oldsilver: #82828c; + --color-davysgrey: #545560; + --color-maximumred: #d72222; + --color-sunglow: #ffd23f; + --color-sunglow-shaded: #977405; + --color-lightninggreen: #26a769; + --color-focus: var(--color-lightninggreen); + /* Variations. */ + --color-lightgray-hover: #c2c3ca; /* 5% darker than base. */ + --color-lightgray-active: #adaeb3; /* 10% darker than base. */ + --color-absolutezero-hover: #0036b1; /* 10% darker than base. */ + --color-absolutezero-active: #00339a; /* 20% darker than base. */ + --color-maximumred-hover: #c11f1f; /* 5% darker than base. */ + --color-maximumred-active: #ab1b1b; /* 10% darker than base. */ + --color-bgblue-hover: #f0f5fd; /* 5% darker than base. */ + --color-bgblue-active: #e6ecf8; /* 10% darker than base. */ + --color-bgred-hover: #fdf5f5; /* 5% darker than base. */ + --color-bgred-active: #fceded; /* 10% darker than base. */ + /* + * Base. + */ + --color-fg: var(--color-text); + --color-bg: var(--color-white); + --color-link: var(--color-absolutezero); + --color-link-hover: var(--color-absolutezero-hover); + --color-link-active: var(--color-absolutezero-active); + --color-divider: rgba(142, 146, 156, 0.5); + /* + * Typography. + */ + --font-family: BlinkMacSystemFont, -apple-system, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + --line-height: 1.5; + --line-height-heading: 1.3; + --font-size-base: 1rem; /* 1rem = 16px if font root is 100% ands browser defaults are used. */ + --font-size-h1: 2.027rem; /* ~32px */ + --font-size-h2: 1.802rem; /* ~29px */ + --font-size-h3: 1.602rem; /* ~26px */ + --font-size-h4: 1.424rem; /* ~23px */ + --font-size-h5: 1.266rem; /* ~20px */ + --font-size-h6: 1.125rem; /* 18px */ + --font-size-s: 0.889rem; /* ~14px */ + --font-size-xs: 0.79rem; /* ~13px */ + --font-size-xxs: 0.702rem; /* ~11px */ + --font-size-label: var(--font-size-s); + --font-size-description: var(--font-size-xs); + /** + * Spaces. + */ + --space-xl: 3rem; /* 3 * 16px = 48px */ + --space-l: 1.5rem; /* 1.5 * 16px = 24px */ + --space-m: 1rem; /* 1 * 16px = 16px */ + --space-s: 0.75rem; /* 0.75 * 16px = 12px */ + --space-xs: 0.5rem; /* 0.5 * 16px = 8px */ + /* + * Common. + */ + --speed-transition: 0.2s; + --transition: all var(--speed-transition) ease-out; + --base-border-radius: 2px; + --focus-border-size: 3px; + --outline-size: 2px; + /* + * Inputs. + */ + --input-fg-color: var(--color-fg); + --input-bg-color: var(--color-bg); + --input-fg-color--description: var(--color-davysgrey); + --input-fg-color--placeholder: var(--color-grayblue); + --input-border-color: var(--color-grayblue); + --input--hover-border-color: var(--color-text); + --input--focus-border-color: var(--color-absolutezero); + --input--focus-shadow-color: rgba(0, 74, 220, 0.3); /* Absolute zero with opacity. */ + --input--error-color: var(--color-maximumred); + --input--error-border-color: var(--color-maximumred); + --input--disabled-color: rgba(84, 85, 96, 0.6); /* Davy's grey with 0.6 opacity. */ + --input--disabled-fg-color: var(--color-oldsilver); + --input--disabled-bg-color: #f2f2f3; /* Light gray with 0.3 opacity on white bg. */ + --input--disabled-border-color: #bababf; /* Old silver with 0.5 opacity on white bg. */ + --input--disabled-border-opacity: 0.5; + --input-border-radius-size: 0.125rem; /* (1/8)em ~ 2px */ + --input-border-size: 1px; /* (1/16)em ~ 1px */ + --input--error-border-size: 2px; + --input-padding-vertical: calc(var(--space-s) - var(--input-border-size)); + --input-padding-horizontal: calc(var(--space-m) - var(--input-border-size)); + --input-font-size: var(--font-size-base); + --input-line-height: var(--space-l); + --input--extrasmall-padding-vertical: calc(0.15rem - var(--input-border-size)); + --input--extrasmall-padding-horizontal: calc(var(--space-xs) - var(--input-border-size)); + --input--extrasmall-font-size: var(--font-size-s); + --input--extrasmall-line-height: calc(var(--space-m) + 0.2rem); /* Font size is too big to use 1rem for extrasmall line-height */ + --input--required-mark-size: 0.4375rem; /* 7px inside the form element label. */ + --input--label-spacing: 1.6875rem; /* 8px with the checkbox width of 19px */ + /* + * Details. + */ + --details-bg-color: rgba(243, 244, 249, 0.4); + --details-border-color: rgba(216, 217, 224, 0.8); + --details-summary-shadow-color: var(--color-focus); + --details-summary-focus-border-size: var(--focus-border-size); + --details-desktop-wrapper-padding-start: calc(var(--space-m) + var(--space-s) + var(--space-xs)); + --details-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + --details-border-size: 1px; + --details-border-size-radius: 2px; + --details-accordion-border-size-radius: var(--base-border-radius); + --details-spread-box-shadow-radius: 2px; + --details-bg-color-transition-duration: 0.12s; + --details-box-shadow-transition-duration: 0.2s; + --details-transform-transition-duration: 0.12s; + /** + * Buttons. + */ + --button--focus-border-color: #5a8bed; + --button-border-radius-size: var(--base-border-radius); + --button-fg-color: var(--color-text); + --button-bg-color: var(--color-lightgray); + --button--hover-bg-color: var(--color-lightgray-hover); + --button--active-bg-color: var(--color-lightgray-active); + --button--disabled-bg-color: #ebebed; + --button--disabled-fg-color: var(--color-grayblue); + --button-fg-color--primary: var(--color-white); + --button-bg-color--primary: var(--color-absolutezero); + --button--hover-bg-color--primary: var(--color-absolutezero-hover); + --button--active-bg-color--primary: var(--color-absolutezero-active); + --button--focus-bg-color--primary: var(--button-bg-color--primary); + --button--disabled-bg-color--primary: var(--color-lightgray); + --button--disabled-fg-color--primary: var(--color-oldsilver); + --button-fg-color--danger: var(--color-white); + --button-bg-color--danger: var(--color-maximumred); + --button--hover-bg-color--danger: var(--color-maximumred-hover); + --button--active-bg-color--danger: var(--color-maximumred-active); + /** + * jQuery.UI dropdown. + */ + --jui-dropdown-fg-color: var(--color-davysgrey); + --jui-dropdown-bg-color: var(--color-white); + --jui-dropdown--active-fg-color: var(--color-white); + --jui-dropdown--active-bg-color: var(--color-absolutezero); + --jui-dropdown-border-color: rgba(216, 217, 224, 0.8); /* Light gray with 0.8 opacity. */ + --jui-dropdown-shadow-color: rgba(34, 35, 48, 0.1); /* Text color with 0.1 opacity. */ + /** + * Progress bar. + */ + --progress-bar-border-size: 1px; + --progress-bar-small-size: calc(var(--space-xs) - (2 * var(--progress-bar-border-size))); + --progress-bar-small-size-radius: var(--space-xs); + --progress-bar-spacing-size: var(--space-xs); + --progress-bar-transition: width 0.5s ease-out; + --progress-bar-label-color: var(--color-text); + --progress-bar-description-color: var(--color-davysgrey); + --progress-bar-description-font-size: var(--font-size-xs); + --progress-track-border-color: var(--color-grayblue); + --progress-track-bg-color: var(--color-lightgray); + /** + * Tabledrag icon size. + */ + --tabledrag-handle-icon-size: calc(17rem / 16); /* 17px */ + /** + * Ajax progress. + */ + --ajax-progress-margin-horizontal: var(--space-s); + /** + * Breadcrumb. + */ + --breadcrumb-height: 1.25rem; +} diff --git a/core/themes/claro/css/src/components/accordion.css b/core/themes/claro/css/src/components/accordion.css new file mode 100644 index 0000000000..223a170058 --- /dev/null +++ b/core/themes/claro/css/src/components/accordion.css @@ -0,0 +1,103 @@ +/* + * DO NOT EDIT THIS FILE. + * See the following change record for more information, + * https://www.drupal.org/node/2815083 + * @preserve + */ + +/** + * @file + * Accordion styles. + */ + +:root { + /* + * Color Palette. + */ + /* Secondary. */ + /* Variations. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 10% darker than base. */ /* 20% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ + /* + * Base. + */ + /* + * Typography. + */ /* 1rem = 16px if font root is 100% ands browser defaults are used. */ /* ~32px */ /* ~29px */ /* ~26px */ /* ~23px */ /* ~20px */ /* 18px */ /* ~14px */ /* ~13px */ /* ~11px */ + /** + * Spaces. + */ /* 3 * 16px = 48px */ /* 1.5 * 16px = 24px */ /* 1 * 16px = 16px */ /* 0.75 * 16px = 12px */ /* 0.5 * 16px = 8px */ + /* + * Common. + */ + /* + * Inputs. + */ /* Absolute zero with opacity. */ /* Davy's grey with 0.6 opacity. */ /* Light gray with 0.3 opacity on white bg. */ /* Old silver with 0.5 opacity on white bg. */ /* (1/8)em ~ 2px */ /* (1/16)em ~ 1px */ /* Font size is too big to use 1rem for extrasmall line-height */ /* 7px inside the form element label. */ /* 8px with the checkbox width of 19px */ + /* + * Details. + */ + /** + * Buttons. + */ + /** + * jQuery.UI dropdown. + */ /* Light gray with 0.8 opacity. */ /* Text color with 0.1 opacity. */ + /** + * Progress bar. + */ + /** + * Tabledrag icon size. + */ /* 17px */ + /** + * Ajax progress. + */ + /** + * Breadcrumb. + */ +} + +.accordion { + color: #222330; + border: 1px solid rgba(216, 217, 224, 0.8); + border-radius: 2px; + background-color: #fff; + -webkit-box-shadow: 0 +2px +4px +rgba(0, 0, 0, 0.1); + box-shadow: 0 +2px +4px +rgba(0, 0, 0, 0.1); +} + +.accordion__item { + margin: 0 -1px; + border-radius: 0; +} + +.accordion__item:first-child { + margin-top: -1px; + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +.accordion__item + .accordion__item { + margin-top: -1px; +} + +.accordion__item:last-child { + margin-bottom: -1px; + border-bottom-right-radius: 2px; + border-bottom-left-radius: 2px; +} + +/** + * Hide JS summary from the details polyfill to make it consistent with native + * details elements. + * + * @todo Consider removing this after https://www.drupal.org/node/2493957 has + * been solved. + */ + +.accordion__item .claro-details__summary .summary { + display: none; +} diff --git a/core/themes/claro/css/src/components/accordion.pcss.css b/core/themes/claro/css/src/components/accordion.pcss.css new file mode 100644 index 0000000000..57961a20db --- /dev/null +++ b/core/themes/claro/css/src/components/accordion.pcss.css @@ -0,0 +1,46 @@ +/** + * @file + * Accordion styles. + */ + +@import "../base/variables.pcss.css"; + +.accordion { + color: var(--color-text); + border: var(--details-border-size) solid var(--details-border-color); + border-radius: var(--details-accordion-border-size-radius); + background-color: var(--color-white); + box-shadow: var(--details-box-shadow); +} + +.accordion__item { + margin: 0 -1px; + border-radius: 0; +} + +.accordion__item:first-child { + margin-top: -1px; + border-top-left-radius: var(--details-accordion-border-size-radius); + border-top-right-radius: var(--details-accordion-border-size-radius); +} + +.accordion__item + .accordion__item { + margin-top: -1px; +} + +.accordion__item:last-child { + margin-bottom: -1px; + border-bottom-right-radius: var(--details-accordion-border-size-radius); + border-bottom-left-radius: var(--details-accordion-border-size-radius); +} + +/** + * Hide JS summary from the details polyfill to make it consistent with native + * details elements. + * + * @todo Consider removing this after https://www.drupal.org/node/2493957 has + * been solved. + */ +.accordion__item .claro-details__summary .summary { + display: none; +} diff --git a/core/themes/claro/css/src/components/action-link.css b/core/themes/claro/css/src/components/action-link.css new file mode 100644 index 0000000000..c76e40f914 --- /dev/null +++ b/core/themes/claro/css/src/components/action-link.css @@ -0,0 +1,498 @@ +/* + * DO NOT EDIT THIS FILE. + * See the following change record for more information, + * https://www.drupal.org/node/2815083 + * @preserve + */ + +/** + * @file + * Styles for action links. + * + * Contains Action link component and the action-links layout styles. + */ + +:root { + /* + * Color Palette. + */ + /* Secondary. */ + /* Variations. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 10% darker than base. */ /* 20% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ + /* + * Base. + */ + /* + * Typography. + */ /* 1rem = 16px if font root is 100% ands browser defaults are used. */ /* ~32px */ /* ~29px */ /* ~26px */ /* ~23px */ /* ~20px */ /* 18px */ /* ~14px */ /* ~13px */ /* ~11px */ + /** + * Spaces. + */ /* 3 * 16px = 48px */ /* 1.5 * 16px = 24px */ /* 1 * 16px = 16px */ /* 0.75 * 16px = 12px */ /* 0.5 * 16px = 8px */ + /* + * Common. + */ + /* + * Inputs. + */ /* Absolute zero with opacity. */ /* Davy's grey with 0.6 opacity. */ /* Light gray with 0.3 opacity on white bg. */ /* Old silver with 0.5 opacity on white bg. */ /* (1/8)em ~ 2px */ /* (1/16)em ~ 1px */ /* Font size is too big to use 1rem for extrasmall line-height */ /* 7px inside the form element label. */ /* 8px with the checkbox width of 19px */ + /* + * Details. + */ + /** + * Buttons. + */ + /** + * jQuery.UI dropdown. + */ /* Light gray with 0.8 opacity. */ /* Text color with 0.1 opacity. */ + /** + * Progress bar. + */ + /** + * Tabledrag icon size. + */ /* 17px */ + /** + * Ajax progress. + */ + /** + * Breadcrumb. + */ +} + +/** + * Action links layout. + */ + +.action-links, +[dir="rtl"] .action-links { + margin: 1.5rem 0; + list-style: none; +} + +.action-links__item { + display: inline-block; +} + +.action-links__item + .action-links__item > .action-link { + margin-left: 0.75rem; /* LTR */ +} + +[dir="rtl"] .action-links__item + .action-links__item > .action-link { + margin-right: 0.75rem; + margin-left: 0; +} + +.action-links__item + .action-links__item > .action-link--small { + margin-left: 0.5rem; /* LTR */ +} + +[dir="rtl"] .action-links__item + .action-links__item > .action-link--small { + margin-right: 0.5rem; + margin-left: 0; +} + +/** + * The action link component. + */ + +.action-link { + display: inline-block; + padding: 0.75rem 1rem; + cursor: pointer; + text-decoration: none; + color: #545560; + border-radius: 2px; + background-color: #fff; + font-size: 1rem; + font-weight: 700; + line-height: 1.5rem; /* Bigger line-height needed to prevent the icon from increasing the height */ + -webkit-font-smoothing: antialiased; +} + +/* Small variant. */ + +.no-touchevents .action-link--small { + padding: 0.375rem 0.75rem; + font-size: 0.889rem; +} + +/* Extra small variant. */ + +.no-touchevents .action-link--extrasmall { + padding: 0 0.5rem; + font-size: 0.889rem; +} + +.action-link + .action-link { + margin-left: 0.75rem; /* LTR */ +} + +[dir="rtl"] .action-link + .action-link { + margin-right: 0.75rem; + margin-left: 0; +} + +.no-touchevents .action-link--small + .action-link--small, +.no-touchevents .action-link--extrasmall + .action-link--extrasmall { + margin-left: 0.5rem; /* LTR */ +} + +[dir="rtl"] .no-touchevents .action-link--small + .action-link--small, +[dir="rtl"] .no-touchevents .action-link--extrasmall + .action-link--extrasmall { + margin-right: 0.5rem; + margin-left: 0; +} + +/** + * Action links inside form-actions. + * + * Add the same margin for action-link inside form-actions as button has. + */ + +.form-actions .action-link { + margin-right: 0.75rem; /* LTR */ + margin-left: 0; /* LTR */ +} + +[dir="rtl"] .form-actions .action-link { + margin-right: 0; + margin-left: 0.75rem; +} + +/* Action link states */ + +.action-link:hover { + text-decoration: none; + color: #0036b1; + background-color: #f0f5fd; +} + +.action-link:focus { + position: relative; + z-index: 1; + text-decoration: none; +} + +.action-link:active { + color: #00339a; + background-color: #e6ecf8; +} + +/** + * Action link variants. + */ + +/* Danger. */ + +.action-link--danger { + color: #d72222; +} + +.action-link--danger:hover { + color: #c11f1f; + background-color: #fdf5f5; +} + +.action-link--danger:active { + color: #ab1b1b; + background-color: #fceded; +} + +/** + * Action link icons with states. + * + * We use parent-relative units here to follow the .action-link's font size. + */ + +/* Defaults for icons */ + +.action-link::before { + position: relative; + top: 0.125rem; /* Set the proper vertical alignment */ + display: inline-block; + width: 1em; + height: 1em; + margin-right: 0.5em; /* LTR */ + margin-left: -0.25rem; /* LTR */ + background-repeat: no-repeat; + background-position: center; + background-size: contain; +} + +[dir="rtl"] .action-link::before { + margin-right: -0.25rem; + margin-left: 0.5em; +} + +.no-touchevents .action-link--small::before, +.no-touchevents .action-link--extrasmall::before { + top: 0.0625rem; /* Set the proper vertical alignment */ + width: 0.75rem; + height: 0.75rem; +} + +.no-touchevents .action-link--extrasmall::before { + margin-right: 0.4em; /* LTR */ + margin-left: -0.125rem; /* LTR */ +} + +[dir="rtl"].no-touchevents .action-link--extrasmall::before { + margin-right: -0.125rem; + margin-left: 0.4em; +} + +/** + * Hide action link icons for IE11. + * + * IE 11 does not display inline svg backgrounds + */ + +@media screen and (-ms-high-contrast: active) { + /* stylelint-disable-next-line selector-type-no-unknown */ + _:-ms-fullscreen, + .action-link::before { + display: none; + } +} + +/* Plus */ + +.action-link--icon-plus::before { + content: ""; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' stroke-width='2' stroke='%23545560'%3E%3Cpath d='m3 8h10'/%3E%3Cpath d='m8 3v10'/%3E%3C/svg%3E"); +} + +.action-link--icon-plus:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' stroke-width='2' stroke='%230036b1'%3E%3Cpath d='m3 8h10'/%3E%3Cpath d='m8 3v10'/%3E%3C/svg%3E"); +} + +.action-link--icon-plus:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' stroke-width='2' stroke='%2300309e'%3E%3Cpath d='m3 8h10'/%3E%3Cpath d='m8 3v10'/%3E%3C/svg%3E"); +} + +/* Plus — danger */ + +.action-link--icon-plus.action-link--danger::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' stroke-width='2' stroke='%23d72222'%3E%3Cpath d='m3 8h10'/%3E%3Cpath d='m8 3v10'/%3E%3C/svg%3E"); +} + +.action-link--icon-plus.action-link--danger:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' stroke-width='2' stroke='%23c11f1f'%3E%3Cpath d='m3 8h10'/%3E%3Cpath d='m8 3v10'/%3E%3C/svg%3E"); +} + +.action-link--icon-plus.action-link--danger:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' stroke-width='2' stroke='%23ab1b1b'%3E%3Cpath d='m3 8h10'/%3E%3Cpath d='m8 3v10'/%3E%3C/svg%3E"); +} + +@media screen and (-ms-high-contrast: active) { + .action-link--icon-plus.action-link--icon-plus.action-link--icon-plus::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' stroke-width='2' stroke='windowText'%3E%3Cpath d='m3 8h10'/%3E%3Cpath d='m8 3v10'/%3E%3C/svg%3E"); + } +} + +/* Trash */ + +.action-link--icon-trash::before { + content: ""; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23545560'%3E%3Cpath d='m14.89965 2.9c-.1-.4-.2-.6-.2-.6-.1-.4-.4-.4-.8-.5l-2.3-.3c-.3 0-.3 0-.4-.3-.4-.7-.5-1.2-.9-1.2h-4.6c-.4 0-.5.5-.9 1.3-.1.2-.1.2-.4.3l-2.3.3c-.4 0-.7.1-.8.4 0 0-.1.2-.2.5-.1.6-.2.5.3.5h13.2c.5 0 .4.1.3-.4zm-1.5 1.8h-10.8c-.7 0-.8.1-.7.6l.8 10.1c.1.5.1.6.8.6h9.1c.6 0 .7-.1.8-.6l.8-10.1c0-.5-.1-.6-.8-.6z'/%3E%3C/svg%3E"); +} + +.action-link--icon-trash:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%230036b1'%3E%3Cpath d='m14.89965 2.9c-.1-.4-.2-.6-.2-.6-.1-.4-.4-.4-.8-.5l-2.3-.3c-.3 0-.3 0-.4-.3-.4-.7-.5-1.2-.9-1.2h-4.6c-.4 0-.5.5-.9 1.3-.1.2-.1.2-.4.3l-2.3.3c-.4 0-.7.1-.8.4 0 0-.1.2-.2.5-.1.6-.2.5.3.5h13.2c.5 0 .4.1.3-.4zm-1.5 1.8h-10.8c-.7 0-.8.1-.7.6l.8 10.1c.1.5.1.6.8.6h9.1c.6 0 .7-.1.8-.6l.8-10.1c0-.5-.1-.6-.8-.6z'/%3E%3C/svg%3E"); +} + +.action-link--icon-trash:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%2300309e'%3E%3Cpath d='m14.89965 2.9c-.1-.4-.2-.6-.2-.6-.1-.4-.4-.4-.8-.5l-2.3-.3c-.3 0-.3 0-.4-.3-.4-.7-.5-1.2-.9-1.2h-4.6c-.4 0-.5.5-.9 1.3-.1.2-.1.2-.4.3l-2.3.3c-.4 0-.7.1-.8.4 0 0-.1.2-.2.5-.1.6-.2.5.3.5h13.2c.5 0 .4.1.3-.4zm-1.5 1.8h-10.8c-.7 0-.8.1-.7.6l.8 10.1c.1.5.1.6.8.6h9.1c.6 0 .7-.1.8-.6l.8-10.1c0-.5-.1-.6-.8-.6z'/%3E%3C/svg%3E"); +} + +/* Trash — danger */ + +.action-link--icon-trash.action-link--danger::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23d72222'%3E%3Cpath d='m14.89965 2.9c-.1-.4-.2-.6-.2-.6-.1-.4-.4-.4-.8-.5l-2.3-.3c-.3 0-.3 0-.4-.3-.4-.7-.5-1.2-.9-1.2h-4.6c-.4 0-.5.5-.9 1.3-.1.2-.1.2-.4.3l-2.3.3c-.4 0-.7.1-.8.4 0 0-.1.2-.2.5-.1.6-.2.5.3.5h13.2c.5 0 .4.1.3-.4zm-1.5 1.8h-10.8c-.7 0-.8.1-.7.6l.8 10.1c.1.5.1.6.8.6h9.1c.6 0 .7-.1.8-.6l.8-10.1c0-.5-.1-.6-.8-.6z'/%3E%3C/svg%3E"); +} + +.action-link--icon-trash.action-link--danger:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23c11f1f'%3E%3Cpath d='m14.89965 2.9c-.1-.4-.2-.6-.2-.6-.1-.4-.4-.4-.8-.5l-2.3-.3c-.3 0-.3 0-.4-.3-.4-.7-.5-1.2-.9-1.2h-4.6c-.4 0-.5.5-.9 1.3-.1.2-.1.2-.4.3l-2.3.3c-.4 0-.7.1-.8.4 0 0-.1.2-.2.5-.1.6-.2.5.3.5h13.2c.5 0 .4.1.3-.4zm-1.5 1.8h-10.8c-.7 0-.8.1-.7.6l.8 10.1c.1.5.1.6.8.6h9.1c.6 0 .7-.1.8-.6l.8-10.1c0-.5-.1-.6-.8-.6z'/%3E%3C/svg%3E"); +} + +.action-link--icon-trash.action-link--danger:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23ab1b1b'%3E%3Cpath d='m14.89965 2.9c-.1-.4-.2-.6-.2-.6-.1-.4-.4-.4-.8-.5l-2.3-.3c-.3 0-.3 0-.4-.3-.4-.7-.5-1.2-.9-1.2h-4.6c-.4 0-.5.5-.9 1.3-.1.2-.1.2-.4.3l-2.3.3c-.4 0-.7.1-.8.4 0 0-.1.2-.2.5-.1.6-.2.5.3.5h13.2c.5 0 .4.1.3-.4zm-1.5 1.8h-10.8c-.7 0-.8.1-.7.6l.8 10.1c.1.5.1.6.8.6h9.1c.6 0 .7-.1.8-.6l.8-10.1c0-.5-.1-.6-.8-.6z'/%3E%3C/svg%3E"); +} + +@media screen and (-ms-high-contrast: active) { + .action-link--icon-trash.action-link--icon-trash.action-link--icon-trash::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='windowText'%3E%3Cpath d='m14.89965 2.9c-.1-.4-.2-.6-.2-.6-.1-.4-.4-.4-.8-.5l-2.3-.3c-.3 0-.3 0-.4-.3-.4-.7-.5-1.2-.9-1.2h-4.6c-.4 0-.5.5-.9 1.3-.1.2-.1.2-.4.3l-2.3.3c-.4 0-.7.1-.8.4 0 0-.1.2-.2.5-.1.6-.2.5.3.5h13.2c.5 0 .4.1.3-.4zm-1.5 1.8h-10.8c-.7 0-.8.1-.7.6l.8 10.1c.1.5.1.6.8.6h9.1c.6 0 .7-.1.8-.6l.8-10.1c0-.5-.1-.6-.8-.6z'/%3E%3C/svg%3E") !important; + } +} + +/* Ex */ + +.action-link--icon-ex::before { + content: ""; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23545560' stroke-width='1.5' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M13 3L3 13'/%3E%3Cpath d='M13 13L3 3'/%3E%3C/svg%3E"); +} + +.action-link--icon-ex:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%230036b1' stroke-width='1.5' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M13 3L3 13'/%3E%3Cpath d='M13 13L3 3'/%3E%3C/svg%3E"); +} + +.action-link--icon-ex:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%2300309e' stroke-width='1.5' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M13 3L3 13'/%3E%3Cpath d='M13 13L3 3'/%3E%3C/svg%3E"); +} + +/* Ex — danger */ + +.action-link--icon-ex.action-link--danger::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23d72222' stroke-width='1.5' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M13 3L3 13'/%3E%3Cpath d='M13 13L3 3'/%3E%3C/svg%3E"); +} + +.action-link--icon-ex.action-link--danger:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23c11f1f' stroke-width='1.5' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M13 3L3 13'/%3E%3Cpath d='M13 13L3 3'/%3E%3C/svg%3E"); +} + +.action-link--icon-ex.action-link--danger:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23ab1b1b' stroke-width='1.5' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M13 3L3 13'/%3E%3Cpath d='M13 13L3 3'/%3E%3C/svg%3E"); +} + +@media screen and (-ms-high-contrast: active) { + .action-link--icon-ex.action-link--icon-ex.action-link--icon-ex::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='windowText' stroke-width='1.5' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M13 3L3 13'/%3E%3Cpath d='M13 13L3 3'/%3E%3C/svg%3E") !important; + } +} + +/* Checkmark */ + +.action-link--icon-checkmark::before { + content: ""; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23545560' stroke-width='2' fill='none' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M2 8.57143L5.6 12L14 4'/%3E%3C/svg%3E"); +} + +.action-link--icon-checkmark:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%230036b1' stroke-width='2' fill='none' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M2 8.57143L5.6 12L14 4'/%3E%3C/svg%3E"); +} + +.action-link--icon-checkmark:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%2300309e' stroke-width='2' fill='none' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M2 8.57143L5.6 12L14 4'/%3E%3C/svg%3E"); +} + +/* Checkmark — danger */ + +.action-link--icon-checkmark.action-link--danger::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23d72222' stroke-width='2' fill='none' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M2 8.57143L5.6 12L14 4'/%3E%3C/svg%3E"); +} + +.action-link--icon-checkmark.action-link--danger:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23c11f1f' stroke-width='2' fill='none' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M2 8.57143L5.6 12L14 4'/%3E%3C/svg%3E"); +} + +.action-link--icon-checkmark.action-link--danger:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23ab1b1b' stroke-width='2' fill='none' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M2 8.57143L5.6 12L14 4'/%3E%3C/svg%3E"); +} + +@media screen and (-ms-high-contrast: active) { + .action-link--icon-checkmark.action-link--icon-checkmark.action-link--icon-checkmark::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='windowText' stroke-width='2' fill='none' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M2 8.57143L5.6 12L14 4'/%3E%3C/svg%3E") !important; + } +} + +/* Cog */ + +.action-link--icon-cog::before { + content: ""; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23545560'%3E%3Cpath d='m15.426 9.249c.045-.327.076-.658.076-.998 0-.36-.035-.71-.086-1.056l-2.275-.293c-.115-.426-.283-.827-.498-1.201l1.396-1.808c-.416-.551-.906-1.039-1.459-1.452l-1.807 1.391c-.373-.215-.774-.383-1.2-.499l-.292-2.252c-.338-.048-.677-.081-1.029-.081s-.694.033-1.032.082l-.291 2.251c-.426.116-.826.284-1.2.499l-1.805-1.391c-.552.413-1.044.901-1.459 1.452l1.395 1.808c-.215.374-.383.774-.499 1.2l-2.276.294c-.05.346-.085.696-.085 1.056 0 .34.031.671.077.998l2.285.295c.115.426.284.826.499 1.2l-1.417 1.836c.411.55.896 1.038 1.443 1.452l1.842-1.42c.374.215.774.383 1.2.498l.298 2.311c.337.047.677.08 1.025.08s.688-.033 1.021-.08l.299-2.311c.426-.115.826-.283 1.201-.498l1.842 1.42c.547-.414 1.031-.902 1.443-1.452l-1.416-1.837c.215-.373.383-.773.498-1.199zm-7.174 1.514c-1.406 0-2.543-1.137-2.543-2.541 0-1.402 1.137-2.541 2.543-2.541 1.402 0 2.541 1.138 2.541 2.541 0 1.404-1.139 2.541-2.541 2.541z'/%3E%3C/svg%3E"); +} + +.action-link--icon-cog:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%230036b1'%3E%3Cpath d='m15.426 9.249c.045-.327.076-.658.076-.998 0-.36-.035-.71-.086-1.056l-2.275-.293c-.115-.426-.283-.827-.498-1.201l1.396-1.808c-.416-.551-.906-1.039-1.459-1.452l-1.807 1.391c-.373-.215-.774-.383-1.2-.499l-.292-2.252c-.338-.048-.677-.081-1.029-.081s-.694.033-1.032.082l-.291 2.251c-.426.116-.826.284-1.2.499l-1.805-1.391c-.552.413-1.044.901-1.459 1.452l1.395 1.808c-.215.374-.383.774-.499 1.2l-2.276.294c-.05.346-.085.696-.085 1.056 0 .34.031.671.077.998l2.285.295c.115.426.284.826.499 1.2l-1.417 1.836c.411.55.896 1.038 1.443 1.452l1.842-1.42c.374.215.774.383 1.2.498l.298 2.311c.337.047.677.08 1.025.08s.688-.033 1.021-.08l.299-2.311c.426-.115.826-.283 1.201-.498l1.842 1.42c.547-.414 1.031-.902 1.443-1.452l-1.416-1.837c.215-.373.383-.773.498-1.199zm-7.174 1.514c-1.406 0-2.543-1.137-2.543-2.541 0-1.402 1.137-2.541 2.543-2.541 1.402 0 2.541 1.138 2.541 2.541 0 1.404-1.139 2.541-2.541 2.541z'/%3E%3C/svg%3E"); +} + +.action-link--icon-cog:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%2300309e'%3E%3Cpath d='m15.426 9.249c.045-.327.076-.658.076-.998 0-.36-.035-.71-.086-1.056l-2.275-.293c-.115-.426-.283-.827-.498-1.201l1.396-1.808c-.416-.551-.906-1.039-1.459-1.452l-1.807 1.391c-.373-.215-.774-.383-1.2-.499l-.292-2.252c-.338-.048-.677-.081-1.029-.081s-.694.033-1.032.082l-.291 2.251c-.426.116-.826.284-1.2.499l-1.805-1.391c-.552.413-1.044.901-1.459 1.452l1.395 1.808c-.215.374-.383.774-.499 1.2l-2.276.294c-.05.346-.085.696-.085 1.056 0 .34.031.671.077.998l2.285.295c.115.426.284.826.499 1.2l-1.417 1.836c.411.55.896 1.038 1.443 1.452l1.842-1.42c.374.215.774.383 1.2.498l.298 2.311c.337.047.677.08 1.025.08s.688-.033 1.021-.08l.299-2.311c.426-.115.826-.283 1.201-.498l1.842 1.42c.547-.414 1.031-.902 1.443-1.452l-1.416-1.837c.215-.373.383-.773.498-1.199zm-7.174 1.514c-1.406 0-2.543-1.137-2.543-2.541 0-1.402 1.137-2.541 2.543-2.541 1.402 0 2.541 1.138 2.541 2.541 0 1.404-1.139 2.541-2.541 2.541z'/%3E%3C/svg%3E"); +} + +/* Cog — danger */ + +.action-link--icon-cog.action-link--danger::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23d72222'%3E%3Cpath d='m15.426 9.249c.045-.327.076-.658.076-.998 0-.36-.035-.71-.086-1.056l-2.275-.293c-.115-.426-.283-.827-.498-1.201l1.396-1.808c-.416-.551-.906-1.039-1.459-1.452l-1.807 1.391c-.373-.215-.774-.383-1.2-.499l-.292-2.252c-.338-.048-.677-.081-1.029-.081s-.694.033-1.032.082l-.291 2.251c-.426.116-.826.284-1.2.499l-1.805-1.391c-.552.413-1.044.901-1.459 1.452l1.395 1.808c-.215.374-.383.774-.499 1.2l-2.276.294c-.05.346-.085.696-.085 1.056 0 .34.031.671.077.998l2.285.295c.115.426.284.826.499 1.2l-1.417 1.836c.411.55.896 1.038 1.443 1.452l1.842-1.42c.374.215.774.383 1.2.498l.298 2.311c.337.047.677.08 1.025.08s.688-.033 1.021-.08l.299-2.311c.426-.115.826-.283 1.201-.498l1.842 1.42c.547-.414 1.031-.902 1.443-1.452l-1.416-1.837c.215-.373.383-.773.498-1.199zm-7.174 1.514c-1.406 0-2.543-1.137-2.543-2.541 0-1.402 1.137-2.541 2.543-2.541 1.402 0 2.541 1.138 2.541 2.541 0 1.404-1.139 2.541-2.541 2.541z'/%3E%3C/svg%3E"); +} + +.action-link--icon-cog.action-link--danger:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23c11f1f'%3E%3Cpath d='m15.426 9.249c.045-.327.076-.658.076-.998 0-.36-.035-.71-.086-1.056l-2.275-.293c-.115-.426-.283-.827-.498-1.201l1.396-1.808c-.416-.551-.906-1.039-1.459-1.452l-1.807 1.391c-.373-.215-.774-.383-1.2-.499l-.292-2.252c-.338-.048-.677-.081-1.029-.081s-.694.033-1.032.082l-.291 2.251c-.426.116-.826.284-1.2.499l-1.805-1.391c-.552.413-1.044.901-1.459 1.452l1.395 1.808c-.215.374-.383.774-.499 1.2l-2.276.294c-.05.346-.085.696-.085 1.056 0 .34.031.671.077.998l2.285.295c.115.426.284.826.499 1.2l-1.417 1.836c.411.55.896 1.038 1.443 1.452l1.842-1.42c.374.215.774.383 1.2.498l.298 2.311c.337.047.677.08 1.025.08s.688-.033 1.021-.08l.299-2.311c.426-.115.826-.283 1.201-.498l1.842 1.42c.547-.414 1.031-.902 1.443-1.452l-1.416-1.837c.215-.373.383-.773.498-1.199zm-7.174 1.514c-1.406 0-2.543-1.137-2.543-2.541 0-1.402 1.137-2.541 2.543-2.541 1.402 0 2.541 1.138 2.541 2.541 0 1.404-1.139 2.541-2.541 2.541z'/%3E%3C/svg%3E"); +} + +.action-link--icon-cog.action-link--danger:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23ab1b1b'%3E%3Cpath d='m15.426 9.249c.045-.327.076-.658.076-.998 0-.36-.035-.71-.086-1.056l-2.275-.293c-.115-.426-.283-.827-.498-1.201l1.396-1.808c-.416-.551-.906-1.039-1.459-1.452l-1.807 1.391c-.373-.215-.774-.383-1.2-.499l-.292-2.252c-.338-.048-.677-.081-1.029-.081s-.694.033-1.032.082l-.291 2.251c-.426.116-.826.284-1.2.499l-1.805-1.391c-.552.413-1.044.901-1.459 1.452l1.395 1.808c-.215.374-.383.774-.499 1.2l-2.276.294c-.05.346-.085.696-.085 1.056 0 .34.031.671.077.998l2.285.295c.115.426.284.826.499 1.2l-1.417 1.836c.411.55.896 1.038 1.443 1.452l1.842-1.42c.374.215.774.383 1.2.498l.298 2.311c.337.047.677.08 1.025.08s.688-.033 1.021-.08l.299-2.311c.426-.115.826-.283 1.201-.498l1.842 1.42c.547-.414 1.031-.902 1.443-1.452l-1.416-1.837c.215-.373.383-.773.498-1.199zm-7.174 1.514c-1.406 0-2.543-1.137-2.543-2.541 0-1.402 1.137-2.541 2.543-2.541 1.402 0 2.541 1.138 2.541 2.541 0 1.404-1.139 2.541-2.541 2.541z'/%3E%3C/svg%3E"); +} + +@media screen and (-ms-high-contrast: active) { + .action-link--icon-cog.action-link--icon-cog.action-link--icon-cog::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='windowText'%3E%3Cpath d='m15.426 9.249c.045-.327.076-.658.076-.998 0-.36-.035-.71-.086-1.056l-2.275-.293c-.115-.426-.283-.827-.498-1.201l1.396-1.808c-.416-.551-.906-1.039-1.459-1.452l-1.807 1.391c-.373-.215-.774-.383-1.2-.499l-.292-2.252c-.338-.048-.677-.081-1.029-.081s-.694.033-1.032.082l-.291 2.251c-.426.116-.826.284-1.2.499l-1.805-1.391c-.552.413-1.044.901-1.459 1.452l1.395 1.808c-.215.374-.383.774-.499 1.2l-2.276.294c-.05.346-.085.696-.085 1.056 0 .34.031.671.077.998l2.285.295c.115.426.284.826.499 1.2l-1.417 1.836c.411.55.896 1.038 1.443 1.452l1.842-1.42c.374.215.774.383 1.2.498l.298 2.311c.337.047.677.08 1.025.08s.688-.033 1.021-.08l.299-2.311c.426-.115.826-.283 1.201-.498l1.842 1.42c.547-.414 1.031-.902 1.443-1.452l-1.416-1.837c.215-.373.383-.773.498-1.199zm-7.174 1.514c-1.406 0-2.543-1.137-2.543-2.541 0-1.402 1.137-2.541 2.543-2.541 1.402 0 2.541 1.138 2.541 2.541 0 1.404-1.139 2.541-2.541 2.541z'/%3E%3C/svg%3E") !important; + } +} + +/* Show */ + +.action-link--icon-show::before { + content: ""; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23545560' fill-rule='evenodd'%3E%3Cpath d='m8 3c-3.6363601 0-6.7418187 2.0733333-8 5 1.2581813 2.926667 4.3636399 5 8 5 3.63636 0 6.741866-2.073333 8-5-1.258134-2.9266667-4.36364-5-8-5zm0 8c1.6568531 0 3-1.343147 3-3 0-1.6568534-1.3431469-3-3-3-1.6568535 0-3 1.3431466-3 3 0 1.656853 1.3431466 3 3 3z'/%3E%3C/svg%3E"); +} + +.action-link--icon-show:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%230036b1' fill-rule='evenodd'%3E%3Cpath d='m8 3c-3.6363601 0-6.7418187 2.0733333-8 5 1.2581813 2.926667 4.3636399 5 8 5 3.63636 0 6.741866-2.073333 8-5-1.258134-2.9266667-4.36364-5-8-5zm0 8c1.6568531 0 3-1.343147 3-3 0-1.6568534-1.3431469-3-3-3-1.6568535 0-3 1.3431466-3 3 0 1.656853 1.3431466 3 3 3z'/%3E%3C/svg%3E"); +} + +.action-link--icon-show:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%2300309e' fill-rule='evenodd'%3E%3Cpath d='m8 3c-3.6363601 0-6.7418187 2.0733333-8 5 1.2581813 2.926667 4.3636399 5 8 5 3.63636 0 6.741866-2.073333 8-5-1.258134-2.9266667-4.36364-5-8-5zm0 8c1.6568531 0 3-1.343147 3-3 0-1.6568534-1.3431469-3-3-3-1.6568535 0-3 1.3431466-3 3 0 1.656853 1.3431466 3 3 3z'/%3E%3C/svg%3E"); +} + +/* Show - danger */ + +.action-link--icon-show.action-link--danger::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23d72222' fill-rule='evenodd'%3E%3Cpath d='m8 3c-3.6363601 0-6.7418187 2.0733333-8 5 1.2581813 2.926667 4.3636399 5 8 5 3.63636 0 6.741866-2.073333 8-5-1.258134-2.9266667-4.36364-5-8-5zm0 8c1.6568531 0 3-1.343147 3-3 0-1.6568534-1.3431469-3-3-3-1.6568535 0-3 1.3431466-3 3 0 1.656853 1.3431466 3 3 3z'/%3E%3C/svg%3E"); +} + +.action-link--icon-show.action-link--danger:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23c11f1f' fill-rule='evenodd'%3E%3Cpath d='m8 3c-3.6363601 0-6.7418187 2.0733333-8 5 1.2581813 2.926667 4.3636399 5 8 5 3.63636 0 6.741866-2.073333 8-5-1.258134-2.9266667-4.36364-5-8-5zm0 8c1.6568531 0 3-1.343147 3-3 0-1.6568534-1.3431469-3-3-3-1.6568535 0-3 1.3431466-3 3 0 1.656853 1.3431466 3 3 3z'/%3E%3C/svg%3E"); +} + +.action-link--icon-show.action-link--danger:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23ab1b1b' fill-rule='evenodd'%3E%3Cpath d='m8 3c-3.6363601 0-6.7418187 2.0733333-8 5 1.2581813 2.926667 4.3636399 5 8 5 3.63636 0 6.741866-2.073333 8-5-1.258134-2.9266667-4.36364-5-8-5zm0 8c1.6568531 0 3-1.343147 3-3 0-1.6568534-1.3431469-3-3-3-1.6568535 0-3 1.3431466-3 3 0 1.656853 1.3431466 3 3 3z'/%3E%3C/svg%3E"); +} + +@media screen and (-ms-high-contrast: active) { + .action-link--icon-show.action-link--icon-show.action-link--icon-show::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='windowText' fill-rule='evenodd'%3E%3Cpath d='m8 3c-3.6363601 0-6.7418187 2.0733333-8 5 1.2581813 2.926667 4.3636399 5 8 5 3.63636 0 6.741866-2.073333 8-5-1.258134-2.9266667-4.36364-5-8-5zm0 8c1.6568531 0 3-1.343147 3-3 0-1.6568534-1.3431469-3-3-3-1.6568535 0-3 1.3431466-3 3 0 1.656853 1.3431466 3 3 3z'/%3E%3C/svg%3E") !important; + } +} + +/* Hide */ + +.action-link--icon-hide::before { + content: ""; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23545560' fill-rule='evenodd'%3E%3Cpath d='m2.0106399 1.6964404-.0106399.0106426 12.072 12.07205-.6964.696467-2.077627-2.077613c-1.015347.38784-2.1292399.602013-3.297973.602013-3.6363601 0-6.7418187-2.073333-8-5 .64703865-1.5050798 1.7826266-2.7844797 3.2277199-3.6722797l-2.2277199-2.2277203.7071066-.707096zm2.98936 6.3035598c0-.54608.1459066-1.0580666.4008533-1.4991333l4.0982932 4.0982801c-.4410666.25496-.9530666.400853-1.4991464.400853-1.6568535 0-3-1.343146-3-3z'/%3E%3Cpath d='m5.1510932 3.4439603 1.75984 1.75984c.3376-.1315867.7048933-.2038 1.0890666-.2038 1.6568533-.0000003 3.0000002 1.3431466 3.0000002 2.9999997 0 .3841735-.07221.7514668-.2038 1.0890668l2.344093 2.3440932c1.269973-.871746 2.26864-2.0582932 2.859707-3.43316-1.258134-2.9266664-4.36364-5-8-5-.9987733 0-1.9575066.1564134-2.8489066.44396z'/%3E%3C/svg%3E"); +} + +.action-link--icon-hide:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%230036b1' fill-rule='evenodd'%3E%3Cpath d='m2.0106399 1.6964404-.0106399.0106426 12.072 12.07205-.6964.696467-2.077627-2.077613c-1.015347.38784-2.1292399.602013-3.297973.602013-3.6363601 0-6.7418187-2.073333-8-5 .64703865-1.5050798 1.7826266-2.7844797 3.2277199-3.6722797l-2.2277199-2.2277203.7071066-.707096zm2.98936 6.3035598c0-.54608.1459066-1.0580666.4008533-1.4991333l4.0982932 4.0982801c-.4410666.25496-.9530666.400853-1.4991464.400853-1.6568535 0-3-1.343146-3-3z'/%3E%3Cpath d='m5.1510932 3.4439603 1.75984 1.75984c.3376-.1315867.7048933-.2038 1.0890666-.2038 1.6568533-.0000003 3.0000002 1.3431466 3.0000002 2.9999997 0 .3841735-.07221.7514668-.2038 1.0890668l2.344093 2.3440932c1.269973-.871746 2.26864-2.0582932 2.859707-3.43316-1.258134-2.9266664-4.36364-5-8-5-.9987733 0-1.9575066.1564134-2.8489066.44396z'/%3E%3C/svg%3E"); +} + +.action-link--icon-hide:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%2300309e' fill-rule='evenodd'%3E%3Cpath d='m2.0106399 1.6964404-.0106399.0106426 12.072 12.07205-.6964.696467-2.077627-2.077613c-1.015347.38784-2.1292399.602013-3.297973.602013-3.6363601 0-6.7418187-2.073333-8-5 .64703865-1.5050798 1.7826266-2.7844797 3.2277199-3.6722797l-2.2277199-2.2277203.7071066-.707096zm2.98936 6.3035598c0-.54608.1459066-1.0580666.4008533-1.4991333l4.0982932 4.0982801c-.4410666.25496-.9530666.400853-1.4991464.400853-1.6568535 0-3-1.343146-3-3z'/%3E%3Cpath d='m5.1510932 3.4439603 1.75984 1.75984c.3376-.1315867.7048933-.2038 1.0890666-.2038 1.6568533-.0000003 3.0000002 1.3431466 3.0000002 2.9999997 0 .3841735-.07221.7514668-.2038 1.0890668l2.344093 2.3440932c1.269973-.871746 2.26864-2.0582932 2.859707-3.43316-1.258134-2.9266664-4.36364-5-8-5-.9987733 0-1.9575066.1564134-2.8489066.44396z'/%3E%3C/svg%3E"); +} + +/* Hide - danger */ + +.action-link--icon-hide.action-link--danger::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23d72222' fill-rule='evenodd'%3E%3Cpath d='m2.0106399 1.6964404-.0106399.0106426 12.072 12.07205-.6964.696467-2.077627-2.077613c-1.015347.38784-2.1292399.602013-3.297973.602013-3.6363601 0-6.7418187-2.073333-8-5 .64703865-1.5050798 1.7826266-2.7844797 3.2277199-3.6722797l-2.2277199-2.2277203.7071066-.707096zm2.98936 6.3035598c0-.54608.1459066-1.0580666.4008533-1.4991333l4.0982932 4.0982801c-.4410666.25496-.9530666.400853-1.4991464.400853-1.6568535 0-3-1.343146-3-3z'/%3E%3Cpath d='m5.1510932 3.4439603 1.75984 1.75984c.3376-.1315867.7048933-.2038 1.0890666-.2038 1.6568533-.0000003 3.0000002 1.3431466 3.0000002 2.9999997 0 .3841735-.07221.7514668-.2038 1.0890668l2.344093 2.3440932c1.269973-.871746 2.26864-2.0582932 2.859707-3.43316-1.258134-2.9266664-4.36364-5-8-5-.9987733 0-1.9575066.1564134-2.8489066.44396z'/%3E%3C/svg%3E"); +} + +.action-link--icon-hide.action-link--danger:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23c11f1f' fill-rule='evenodd'%3E%3Cpath d='m2.0106399 1.6964404-.0106399.0106426 12.072 12.07205-.6964.696467-2.077627-2.077613c-1.015347.38784-2.1292399.602013-3.297973.602013-3.6363601 0-6.7418187-2.073333-8-5 .64703865-1.5050798 1.7826266-2.7844797 3.2277199-3.6722797l-2.2277199-2.2277203.7071066-.707096zm2.98936 6.3035598c0-.54608.1459066-1.0580666.4008533-1.4991333l4.0982932 4.0982801c-.4410666.25496-.9530666.400853-1.4991464.400853-1.6568535 0-3-1.343146-3-3z'/%3E%3Cpath d='m5.1510932 3.4439603 1.75984 1.75984c.3376-.1315867.7048933-.2038 1.0890666-.2038 1.6568533-.0000003 3.0000002 1.3431466 3.0000002 2.9999997 0 .3841735-.07221.7514668-.2038 1.0890668l2.344093 2.3440932c1.269973-.871746 2.26864-2.0582932 2.859707-3.43316-1.258134-2.9266664-4.36364-5-8-5-.9987733 0-1.9575066.1564134-2.8489066.44396z'/%3E%3C/svg%3E"); +} + +.action-link--icon-hide.action-link--danger:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23ab1b1b' fill-rule='evenodd'%3E%3Cpath d='m2.0106399 1.6964404-.0106399.0106426 12.072 12.07205-.6964.696467-2.077627-2.077613c-1.015347.38784-2.1292399.602013-3.297973.602013-3.6363601 0-6.7418187-2.073333-8-5 .64703865-1.5050798 1.7826266-2.7844797 3.2277199-3.6722797l-2.2277199-2.2277203.7071066-.707096zm2.98936 6.3035598c0-.54608.1459066-1.0580666.4008533-1.4991333l4.0982932 4.0982801c-.4410666.25496-.9530666.400853-1.4991464.400853-1.6568535 0-3-1.343146-3-3z'/%3E%3Cpath d='m5.1510932 3.4439603 1.75984 1.75984c.3376-.1315867.7048933-.2038 1.0890666-.2038 1.6568533-.0000003 3.0000002 1.3431466 3.0000002 2.9999997 0 .3841735-.07221.7514668-.2038 1.0890668l2.344093 2.3440932c1.269973-.871746 2.26864-2.0582932 2.859707-3.43316-1.258134-2.9266664-4.36364-5-8-5-.9987733 0-1.9575066.1564134-2.8489066.44396z'/%3E%3C/svg%3E"); +} + +@media screen and (-ms-high-contrast: active) { + .action-link--icon-hide.action-link--icon-hide.action-link--icon-hide::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='windowText' fill-rule='evenodd'%3E%3Cpath d='m2.0106399 1.6964404-.0106399.0106426 12.072 12.07205-.6964.696467-2.077627-2.077613c-1.015347.38784-2.1292399.602013-3.297973.602013-3.6363601 0-6.7418187-2.073333-8-5 .64703865-1.5050798 1.7826266-2.7844797 3.2277199-3.6722797l-2.2277199-2.2277203.7071066-.707096zm2.98936 6.3035598c0-.54608.1459066-1.0580666.4008533-1.4991333l4.0982932 4.0982801c-.4410666.25496-.9530666.400853-1.4991464.400853-1.6568535 0-3-1.343146-3-3z'/%3E%3Cpath d='m5.1510932 3.4439603 1.75984 1.75984c.3376-.1315867.7048933-.2038 1.0890666-.2038 1.6568533-.0000003 3.0000002 1.3431466 3.0000002 2.9999997 0 .3841735-.07221.7514668-.2038 1.0890668l2.344093 2.3440932c1.269973-.871746 2.26864-2.0582932 2.859707-3.43316-1.258134-2.9266664-4.36364-5-8-5-.9987733 0-1.9575066.1564134-2.8489066.44396z'/%3E%3C/svg%3E"); + } +} diff --git a/core/themes/claro/css/src/components/action-link.pcss.css b/core/themes/claro/css/src/components/action-link.pcss.css new file mode 100644 index 0000000000..30af86a232 --- /dev/null +++ b/core/themes/claro/css/src/components/action-link.pcss.css @@ -0,0 +1,386 @@ +/** + * @file + * Styles for action links. + * + * Contains Action link component and the action-links layout styles. + */ + +@import "../base/variables.pcss.css"; + +/** + * Action links layout. + */ +.action-links, +[dir="rtl"] .action-links { + margin: var(--space-l) 0; + list-style: none; +} + +.action-links__item { + display: inline-block; +} + +.action-links__item + .action-links__item > .action-link { + margin-left: var(--space-s); /* LTR */ +} +[dir="rtl"] .action-links__item + .action-links__item > .action-link { + margin-right: var(--space-s); + margin-left: 0; +} + +.action-links__item + .action-links__item > .action-link--small { + margin-left: var(--space-xs); /* LTR */ +} +[dir="rtl"] .action-links__item + .action-links__item > .action-link--small { + margin-right: var(--space-xs); + margin-left: 0; +} + +/** + * The action link component. + */ +.action-link { + display: inline-block; + padding: calc(var(--space-m) - ((var(--space-l) - var(--space-m)) / 2)) var(--space-m); + cursor: pointer; + text-decoration: none; + color: var(--color-davysgrey); + border-radius: var(--button-border-radius-size); + background-color: var(--color-bg); + font-size: var(--font-size-base); + font-weight: 700; + line-height: var(--space-l); /* Bigger line-height needed to prevent the icon from increasing the height */ + -webkit-font-smoothing: antialiased; +} + +/* Small variant. */ +.no-touchevents .action-link--small { + padding: calc(var(--space-s) - ((var(--space-l) - var(--space-s)) / 2)) var(--space-s); + font-size: var(--font-size-s); +} + +/* Extra small variant. */ +.no-touchevents .action-link--extrasmall { + padding: 0 var(--space-xs); + font-size: var(--font-size-s); +} + +.action-link + .action-link { + margin-left: var(--space-s); /* LTR */ +} +[dir="rtl"] .action-link + .action-link { + margin-right: var(--space-s); + margin-left: 0; +} +.no-touchevents .action-link--small + .action-link--small, +.no-touchevents .action-link--extrasmall + .action-link--extrasmall { + margin-left: var(--space-xs); /* LTR */ +} +[dir="rtl"] .no-touchevents .action-link--small + .action-link--small, +[dir="rtl"] .no-touchevents .action-link--extrasmall + .action-link--extrasmall { + margin-right: var(--space-xs); + margin-left: 0; +} + +/** + * Action links inside form-actions. + * + * Add the same margin for action-link inside form-actions as button has. + */ +.form-actions .action-link { + margin-right: var(--space-s); /* LTR */ + margin-left: 0; /* LTR */ +} +[dir="rtl"] .form-actions .action-link { + margin-right: 0; + margin-left: var(--space-s); +} + +/* Action link states */ +.action-link:hover { + text-decoration: none; + color: var(--color-absolutezero-hover); + background-color: var(--color-bgblue-hover); +} +.action-link:focus { + position: relative; + z-index: 1; + text-decoration: none; +} +.action-link:active { + color: var(--color-absolutezero-active); + background-color: var(--color-bgblue-active); +} + +/** + * Action link variants. + */ +/* Danger. */ +.action-link--danger { + color: var(--color-maximumred); +} +.action-link--danger:hover { + color: var(--color-maximumred-hover); + background-color: var(--color-bgred-hover); +} +.action-link--danger:active { + color: var(--color-maximumred-active); + background-color: var(--color-bgred-active); +} + +/** + * Action link icons with states. + * + * We use parent-relative units here to follow the .action-link's font size. + */ + +/* Defaults for icons */ +.action-link::before { + position: relative; + top: 0.125rem; /* Set the proper vertical alignment */ + display: inline-block; + width: 1em; + height: 1em; + margin-right: 0.5em; /* LTR */ + margin-left: calc(var(--space-s) - var(--space-m)); /* LTR */ + background-repeat: no-repeat; + background-position: center; + background-size: contain; +} +[dir="rtl"] .action-link::before { + margin-right: calc(var(--space-s) - var(--space-m)); + margin-left: 0.5em; +} + +.no-touchevents .action-link--small::before, +.no-touchevents .action-link--extrasmall::before { + top: 0.0625rem; /* Set the proper vertical alignment */ + width: var(--space-s); + height: var(--space-s); +} + +.no-touchevents .action-link--extrasmall::before { + margin-right: 0.4em; /* LTR */ + margin-left: -0.125rem; /* LTR */ +} +[dir="rtl"].no-touchevents .action-link--extrasmall::before { + margin-right: -0.125rem; + margin-left: 0.4em; +} + +/** + * Hide action link icons for IE11. + * + * IE 11 does not display inline svg backgrounds + */ +@media screen and (-ms-high-contrast: active) { + /* stylelint-disable-next-line selector-type-no-unknown */ + _:-ms-fullscreen, + .action-link::before { + display: none; + } +} + +/* Plus */ +.action-link--icon-plus::before { + content: ""; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' stroke-width='2' stroke='%23545560'%3E%3Cpath d='m3 8h10'/%3E%3Cpath d='m8 3v10'/%3E%3C/svg%3E"); +} + +.action-link--icon-plus:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' stroke-width='2' stroke='%230036b1'%3E%3Cpath d='m3 8h10'/%3E%3Cpath d='m8 3v10'/%3E%3C/svg%3E"); +} +.action-link--icon-plus:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' stroke-width='2' stroke='%2300309e'%3E%3Cpath d='m3 8h10'/%3E%3Cpath d='m8 3v10'/%3E%3C/svg%3E"); +} + +/* Plus — danger */ +.action-link--icon-plus.action-link--danger::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' stroke-width='2' stroke='%23d72222'%3E%3Cpath d='m3 8h10'/%3E%3Cpath d='m8 3v10'/%3E%3C/svg%3E"); +} +.action-link--icon-plus.action-link--danger:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' stroke-width='2' stroke='%23c11f1f'%3E%3Cpath d='m3 8h10'/%3E%3Cpath d='m8 3v10'/%3E%3C/svg%3E"); +} +.action-link--icon-plus.action-link--danger:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' stroke-width='2' stroke='%23ab1b1b'%3E%3Cpath d='m3 8h10'/%3E%3Cpath d='m8 3v10'/%3E%3C/svg%3E"); +} + +@media screen and (-ms-high-contrast: active) { + .action-link--icon-plus.action-link--icon-plus.action-link--icon-plus::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' stroke-width='2' stroke='windowText'%3E%3Cpath d='m3 8h10'/%3E%3Cpath d='m8 3v10'/%3E%3C/svg%3E"); + } +} + +/* Trash */ +.action-link--icon-trash::before { + content: ""; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23545560'%3E%3Cpath d='m14.89965 2.9c-.1-.4-.2-.6-.2-.6-.1-.4-.4-.4-.8-.5l-2.3-.3c-.3 0-.3 0-.4-.3-.4-.7-.5-1.2-.9-1.2h-4.6c-.4 0-.5.5-.9 1.3-.1.2-.1.2-.4.3l-2.3.3c-.4 0-.7.1-.8.4 0 0-.1.2-.2.5-.1.6-.2.5.3.5h13.2c.5 0 .4.1.3-.4zm-1.5 1.8h-10.8c-.7 0-.8.1-.7.6l.8 10.1c.1.5.1.6.8.6h9.1c.6 0 .7-.1.8-.6l.8-10.1c0-.5-.1-.6-.8-.6z'/%3E%3C/svg%3E"); +} +.action-link--icon-trash:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%230036b1'%3E%3Cpath d='m14.89965 2.9c-.1-.4-.2-.6-.2-.6-.1-.4-.4-.4-.8-.5l-2.3-.3c-.3 0-.3 0-.4-.3-.4-.7-.5-1.2-.9-1.2h-4.6c-.4 0-.5.5-.9 1.3-.1.2-.1.2-.4.3l-2.3.3c-.4 0-.7.1-.8.4 0 0-.1.2-.2.5-.1.6-.2.5.3.5h13.2c.5 0 .4.1.3-.4zm-1.5 1.8h-10.8c-.7 0-.8.1-.7.6l.8 10.1c.1.5.1.6.8.6h9.1c.6 0 .7-.1.8-.6l.8-10.1c0-.5-.1-.6-.8-.6z'/%3E%3C/svg%3E"); +} +.action-link--icon-trash:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%2300309e'%3E%3Cpath d='m14.89965 2.9c-.1-.4-.2-.6-.2-.6-.1-.4-.4-.4-.8-.5l-2.3-.3c-.3 0-.3 0-.4-.3-.4-.7-.5-1.2-.9-1.2h-4.6c-.4 0-.5.5-.9 1.3-.1.2-.1.2-.4.3l-2.3.3c-.4 0-.7.1-.8.4 0 0-.1.2-.2.5-.1.6-.2.5.3.5h13.2c.5 0 .4.1.3-.4zm-1.5 1.8h-10.8c-.7 0-.8.1-.7.6l.8 10.1c.1.5.1.6.8.6h9.1c.6 0 .7-.1.8-.6l.8-10.1c0-.5-.1-.6-.8-.6z'/%3E%3C/svg%3E"); +} + +/* Trash — danger */ +.action-link--icon-trash.action-link--danger::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23d72222'%3E%3Cpath d='m14.89965 2.9c-.1-.4-.2-.6-.2-.6-.1-.4-.4-.4-.8-.5l-2.3-.3c-.3 0-.3 0-.4-.3-.4-.7-.5-1.2-.9-1.2h-4.6c-.4 0-.5.5-.9 1.3-.1.2-.1.2-.4.3l-2.3.3c-.4 0-.7.1-.8.4 0 0-.1.2-.2.5-.1.6-.2.5.3.5h13.2c.5 0 .4.1.3-.4zm-1.5 1.8h-10.8c-.7 0-.8.1-.7.6l.8 10.1c.1.5.1.6.8.6h9.1c.6 0 .7-.1.8-.6l.8-10.1c0-.5-.1-.6-.8-.6z'/%3E%3C/svg%3E"); +} +.action-link--icon-trash.action-link--danger:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23c11f1f'%3E%3Cpath d='m14.89965 2.9c-.1-.4-.2-.6-.2-.6-.1-.4-.4-.4-.8-.5l-2.3-.3c-.3 0-.3 0-.4-.3-.4-.7-.5-1.2-.9-1.2h-4.6c-.4 0-.5.5-.9 1.3-.1.2-.1.2-.4.3l-2.3.3c-.4 0-.7.1-.8.4 0 0-.1.2-.2.5-.1.6-.2.5.3.5h13.2c.5 0 .4.1.3-.4zm-1.5 1.8h-10.8c-.7 0-.8.1-.7.6l.8 10.1c.1.5.1.6.8.6h9.1c.6 0 .7-.1.8-.6l.8-10.1c0-.5-.1-.6-.8-.6z'/%3E%3C/svg%3E"); +} +.action-link--icon-trash.action-link--danger:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23ab1b1b'%3E%3Cpath d='m14.89965 2.9c-.1-.4-.2-.6-.2-.6-.1-.4-.4-.4-.8-.5l-2.3-.3c-.3 0-.3 0-.4-.3-.4-.7-.5-1.2-.9-1.2h-4.6c-.4 0-.5.5-.9 1.3-.1.2-.1.2-.4.3l-2.3.3c-.4 0-.7.1-.8.4 0 0-.1.2-.2.5-.1.6-.2.5.3.5h13.2c.5 0 .4.1.3-.4zm-1.5 1.8h-10.8c-.7 0-.8.1-.7.6l.8 10.1c.1.5.1.6.8.6h9.1c.6 0 .7-.1.8-.6l.8-10.1c0-.5-.1-.6-.8-.6z'/%3E%3C/svg%3E"); +} + +@media screen and (-ms-high-contrast: active) { + .action-link--icon-trash.action-link--icon-trash.action-link--icon-trash::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='windowText'%3E%3Cpath d='m14.89965 2.9c-.1-.4-.2-.6-.2-.6-.1-.4-.4-.4-.8-.5l-2.3-.3c-.3 0-.3 0-.4-.3-.4-.7-.5-1.2-.9-1.2h-4.6c-.4 0-.5.5-.9 1.3-.1.2-.1.2-.4.3l-2.3.3c-.4 0-.7.1-.8.4 0 0-.1.2-.2.5-.1.6-.2.5.3.5h13.2c.5 0 .4.1.3-.4zm-1.5 1.8h-10.8c-.7 0-.8.1-.7.6l.8 10.1c.1.5.1.6.8.6h9.1c.6 0 .7-.1.8-.6l.8-10.1c0-.5-.1-.6-.8-.6z'/%3E%3C/svg%3E") !important; + } +} + +/* Ex */ +.action-link--icon-ex::before { + content: ""; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23545560' stroke-width='1.5' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M13 3L3 13'/%3E%3Cpath d='M13 13L3 3'/%3E%3C/svg%3E"); +} +.action-link--icon-ex:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%230036b1' stroke-width='1.5' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M13 3L3 13'/%3E%3Cpath d='M13 13L3 3'/%3E%3C/svg%3E"); +} +.action-link--icon-ex:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%2300309e' stroke-width='1.5' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M13 3L3 13'/%3E%3Cpath d='M13 13L3 3'/%3E%3C/svg%3E"); +} + +/* Ex — danger */ +.action-link--icon-ex.action-link--danger::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23d72222' stroke-width='1.5' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M13 3L3 13'/%3E%3Cpath d='M13 13L3 3'/%3E%3C/svg%3E"); +} +.action-link--icon-ex.action-link--danger:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23c11f1f' stroke-width='1.5' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M13 3L3 13'/%3E%3Cpath d='M13 13L3 3'/%3E%3C/svg%3E"); +} +.action-link--icon-ex.action-link--danger:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23ab1b1b' stroke-width='1.5' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M13 3L3 13'/%3E%3Cpath d='M13 13L3 3'/%3E%3C/svg%3E"); +} + +@media screen and (-ms-high-contrast: active) { + .action-link--icon-ex.action-link--icon-ex.action-link--icon-ex::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='windowText' stroke-width='1.5' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M13 3L3 13'/%3E%3Cpath d='M13 13L3 3'/%3E%3C/svg%3E") !important; + } +} + +/* Checkmark */ +.action-link--icon-checkmark::before { + content: ""; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23545560' stroke-width='2' fill='none' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M2 8.57143L5.6 12L14 4'/%3E%3C/svg%3E"); +} +.action-link--icon-checkmark:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%230036b1' stroke-width='2' fill='none' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M2 8.57143L5.6 12L14 4'/%3E%3C/svg%3E"); +} +.action-link--icon-checkmark:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%2300309e' stroke-width='2' fill='none' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M2 8.57143L5.6 12L14 4'/%3E%3C/svg%3E"); +} + +/* Checkmark — danger */ +.action-link--icon-checkmark.action-link--danger::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23d72222' stroke-width='2' fill='none' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M2 8.57143L5.6 12L14 4'/%3E%3C/svg%3E"); +} +.action-link--icon-checkmark.action-link--danger:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23c11f1f' stroke-width='2' fill='none' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M2 8.57143L5.6 12L14 4'/%3E%3C/svg%3E"); +} +.action-link--icon-checkmark.action-link--danger:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='%23ab1b1b' stroke-width='2' fill='none' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M2 8.57143L5.6 12L14 4'/%3E%3C/svg%3E"); +} + +@media screen and (-ms-high-contrast: active) { + .action-link--icon-checkmark.action-link--icon-checkmark.action-link--icon-checkmark::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke='windowText' stroke-width='2' fill='none' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M2 8.57143L5.6 12L14 4'/%3E%3C/svg%3E") !important; + } +} + +/* Cog */ +.action-link--icon-cog::before { + content: ""; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23545560'%3E%3Cpath d='m15.426 9.249c.045-.327.076-.658.076-.998 0-.36-.035-.71-.086-1.056l-2.275-.293c-.115-.426-.283-.827-.498-1.201l1.396-1.808c-.416-.551-.906-1.039-1.459-1.452l-1.807 1.391c-.373-.215-.774-.383-1.2-.499l-.292-2.252c-.338-.048-.677-.081-1.029-.081s-.694.033-1.032.082l-.291 2.251c-.426.116-.826.284-1.2.499l-1.805-1.391c-.552.413-1.044.901-1.459 1.452l1.395 1.808c-.215.374-.383.774-.499 1.2l-2.276.294c-.05.346-.085.696-.085 1.056 0 .34.031.671.077.998l2.285.295c.115.426.284.826.499 1.2l-1.417 1.836c.411.55.896 1.038 1.443 1.452l1.842-1.42c.374.215.774.383 1.2.498l.298 2.311c.337.047.677.08 1.025.08s.688-.033 1.021-.08l.299-2.311c.426-.115.826-.283 1.201-.498l1.842 1.42c.547-.414 1.031-.902 1.443-1.452l-1.416-1.837c.215-.373.383-.773.498-1.199zm-7.174 1.514c-1.406 0-2.543-1.137-2.543-2.541 0-1.402 1.137-2.541 2.543-2.541 1.402 0 2.541 1.138 2.541 2.541 0 1.404-1.139 2.541-2.541 2.541z'/%3E%3C/svg%3E"); +} +.action-link--icon-cog:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%230036b1'%3E%3Cpath d='m15.426 9.249c.045-.327.076-.658.076-.998 0-.36-.035-.71-.086-1.056l-2.275-.293c-.115-.426-.283-.827-.498-1.201l1.396-1.808c-.416-.551-.906-1.039-1.459-1.452l-1.807 1.391c-.373-.215-.774-.383-1.2-.499l-.292-2.252c-.338-.048-.677-.081-1.029-.081s-.694.033-1.032.082l-.291 2.251c-.426.116-.826.284-1.2.499l-1.805-1.391c-.552.413-1.044.901-1.459 1.452l1.395 1.808c-.215.374-.383.774-.499 1.2l-2.276.294c-.05.346-.085.696-.085 1.056 0 .34.031.671.077.998l2.285.295c.115.426.284.826.499 1.2l-1.417 1.836c.411.55.896 1.038 1.443 1.452l1.842-1.42c.374.215.774.383 1.2.498l.298 2.311c.337.047.677.08 1.025.08s.688-.033 1.021-.08l.299-2.311c.426-.115.826-.283 1.201-.498l1.842 1.42c.547-.414 1.031-.902 1.443-1.452l-1.416-1.837c.215-.373.383-.773.498-1.199zm-7.174 1.514c-1.406 0-2.543-1.137-2.543-2.541 0-1.402 1.137-2.541 2.543-2.541 1.402 0 2.541 1.138 2.541 2.541 0 1.404-1.139 2.541-2.541 2.541z'/%3E%3C/svg%3E"); +} +.action-link--icon-cog:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%2300309e'%3E%3Cpath d='m15.426 9.249c.045-.327.076-.658.076-.998 0-.36-.035-.71-.086-1.056l-2.275-.293c-.115-.426-.283-.827-.498-1.201l1.396-1.808c-.416-.551-.906-1.039-1.459-1.452l-1.807 1.391c-.373-.215-.774-.383-1.2-.499l-.292-2.252c-.338-.048-.677-.081-1.029-.081s-.694.033-1.032.082l-.291 2.251c-.426.116-.826.284-1.2.499l-1.805-1.391c-.552.413-1.044.901-1.459 1.452l1.395 1.808c-.215.374-.383.774-.499 1.2l-2.276.294c-.05.346-.085.696-.085 1.056 0 .34.031.671.077.998l2.285.295c.115.426.284.826.499 1.2l-1.417 1.836c.411.55.896 1.038 1.443 1.452l1.842-1.42c.374.215.774.383 1.2.498l.298 2.311c.337.047.677.08 1.025.08s.688-.033 1.021-.08l.299-2.311c.426-.115.826-.283 1.201-.498l1.842 1.42c.547-.414 1.031-.902 1.443-1.452l-1.416-1.837c.215-.373.383-.773.498-1.199zm-7.174 1.514c-1.406 0-2.543-1.137-2.543-2.541 0-1.402 1.137-2.541 2.543-2.541 1.402 0 2.541 1.138 2.541 2.541 0 1.404-1.139 2.541-2.541 2.541z'/%3E%3C/svg%3E"); +} + +/* Cog — danger */ +.action-link--icon-cog.action-link--danger::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23d72222'%3E%3Cpath d='m15.426 9.249c.045-.327.076-.658.076-.998 0-.36-.035-.71-.086-1.056l-2.275-.293c-.115-.426-.283-.827-.498-1.201l1.396-1.808c-.416-.551-.906-1.039-1.459-1.452l-1.807 1.391c-.373-.215-.774-.383-1.2-.499l-.292-2.252c-.338-.048-.677-.081-1.029-.081s-.694.033-1.032.082l-.291 2.251c-.426.116-.826.284-1.2.499l-1.805-1.391c-.552.413-1.044.901-1.459 1.452l1.395 1.808c-.215.374-.383.774-.499 1.2l-2.276.294c-.05.346-.085.696-.085 1.056 0 .34.031.671.077.998l2.285.295c.115.426.284.826.499 1.2l-1.417 1.836c.411.55.896 1.038 1.443 1.452l1.842-1.42c.374.215.774.383 1.2.498l.298 2.311c.337.047.677.08 1.025.08s.688-.033 1.021-.08l.299-2.311c.426-.115.826-.283 1.201-.498l1.842 1.42c.547-.414 1.031-.902 1.443-1.452l-1.416-1.837c.215-.373.383-.773.498-1.199zm-7.174 1.514c-1.406 0-2.543-1.137-2.543-2.541 0-1.402 1.137-2.541 2.543-2.541 1.402 0 2.541 1.138 2.541 2.541 0 1.404-1.139 2.541-2.541 2.541z'/%3E%3C/svg%3E"); +} +.action-link--icon-cog.action-link--danger:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23c11f1f'%3E%3Cpath d='m15.426 9.249c.045-.327.076-.658.076-.998 0-.36-.035-.71-.086-1.056l-2.275-.293c-.115-.426-.283-.827-.498-1.201l1.396-1.808c-.416-.551-.906-1.039-1.459-1.452l-1.807 1.391c-.373-.215-.774-.383-1.2-.499l-.292-2.252c-.338-.048-.677-.081-1.029-.081s-.694.033-1.032.082l-.291 2.251c-.426.116-.826.284-1.2.499l-1.805-1.391c-.552.413-1.044.901-1.459 1.452l1.395 1.808c-.215.374-.383.774-.499 1.2l-2.276.294c-.05.346-.085.696-.085 1.056 0 .34.031.671.077.998l2.285.295c.115.426.284.826.499 1.2l-1.417 1.836c.411.55.896 1.038 1.443 1.452l1.842-1.42c.374.215.774.383 1.2.498l.298 2.311c.337.047.677.08 1.025.08s.688-.033 1.021-.08l.299-2.311c.426-.115.826-.283 1.201-.498l1.842 1.42c.547-.414 1.031-.902 1.443-1.452l-1.416-1.837c.215-.373.383-.773.498-1.199zm-7.174 1.514c-1.406 0-2.543-1.137-2.543-2.541 0-1.402 1.137-2.541 2.543-2.541 1.402 0 2.541 1.138 2.541 2.541 0 1.404-1.139 2.541-2.541 2.541z'/%3E%3C/svg%3E"); +} +.action-link--icon-cog.action-link--danger:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23ab1b1b'%3E%3Cpath d='m15.426 9.249c.045-.327.076-.658.076-.998 0-.36-.035-.71-.086-1.056l-2.275-.293c-.115-.426-.283-.827-.498-1.201l1.396-1.808c-.416-.551-.906-1.039-1.459-1.452l-1.807 1.391c-.373-.215-.774-.383-1.2-.499l-.292-2.252c-.338-.048-.677-.081-1.029-.081s-.694.033-1.032.082l-.291 2.251c-.426.116-.826.284-1.2.499l-1.805-1.391c-.552.413-1.044.901-1.459 1.452l1.395 1.808c-.215.374-.383.774-.499 1.2l-2.276.294c-.05.346-.085.696-.085 1.056 0 .34.031.671.077.998l2.285.295c.115.426.284.826.499 1.2l-1.417 1.836c.411.55.896 1.038 1.443 1.452l1.842-1.42c.374.215.774.383 1.2.498l.298 2.311c.337.047.677.08 1.025.08s.688-.033 1.021-.08l.299-2.311c.426-.115.826-.283 1.201-.498l1.842 1.42c.547-.414 1.031-.902 1.443-1.452l-1.416-1.837c.215-.373.383-.773.498-1.199zm-7.174 1.514c-1.406 0-2.543-1.137-2.543-2.541 0-1.402 1.137-2.541 2.543-2.541 1.402 0 2.541 1.138 2.541 2.541 0 1.404-1.139 2.541-2.541 2.541z'/%3E%3C/svg%3E"); +} + +@media screen and (-ms-high-contrast: active) { + .action-link--icon-cog.action-link--icon-cog.action-link--icon-cog::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='windowText'%3E%3Cpath d='m15.426 9.249c.045-.327.076-.658.076-.998 0-.36-.035-.71-.086-1.056l-2.275-.293c-.115-.426-.283-.827-.498-1.201l1.396-1.808c-.416-.551-.906-1.039-1.459-1.452l-1.807 1.391c-.373-.215-.774-.383-1.2-.499l-.292-2.252c-.338-.048-.677-.081-1.029-.081s-.694.033-1.032.082l-.291 2.251c-.426.116-.826.284-1.2.499l-1.805-1.391c-.552.413-1.044.901-1.459 1.452l1.395 1.808c-.215.374-.383.774-.499 1.2l-2.276.294c-.05.346-.085.696-.085 1.056 0 .34.031.671.077.998l2.285.295c.115.426.284.826.499 1.2l-1.417 1.836c.411.55.896 1.038 1.443 1.452l1.842-1.42c.374.215.774.383 1.2.498l.298 2.311c.337.047.677.08 1.025.08s.688-.033 1.021-.08l.299-2.311c.426-.115.826-.283 1.201-.498l1.842 1.42c.547-.414 1.031-.902 1.443-1.452l-1.416-1.837c.215-.373.383-.773.498-1.199zm-7.174 1.514c-1.406 0-2.543-1.137-2.543-2.541 0-1.402 1.137-2.541 2.543-2.541 1.402 0 2.541 1.138 2.541 2.541 0 1.404-1.139 2.541-2.541 2.541z'/%3E%3C/svg%3E") !important; + } +} + +/* Show */ +.action-link--icon-show::before { + content: ""; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23545560' fill-rule='evenodd'%3E%3Cpath d='m8 3c-3.6363601 0-6.7418187 2.0733333-8 5 1.2581813 2.926667 4.3636399 5 8 5 3.63636 0 6.741866-2.073333 8-5-1.258134-2.9266667-4.36364-5-8-5zm0 8c1.6568531 0 3-1.343147 3-3 0-1.6568534-1.3431469-3-3-3-1.6568535 0-3 1.3431466-3 3 0 1.656853 1.3431466 3 3 3z'/%3E%3C/svg%3E"); +} +.action-link--icon-show:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%230036b1' fill-rule='evenodd'%3E%3Cpath d='m8 3c-3.6363601 0-6.7418187 2.0733333-8 5 1.2581813 2.926667 4.3636399 5 8 5 3.63636 0 6.741866-2.073333 8-5-1.258134-2.9266667-4.36364-5-8-5zm0 8c1.6568531 0 3-1.343147 3-3 0-1.6568534-1.3431469-3-3-3-1.6568535 0-3 1.3431466-3 3 0 1.656853 1.3431466 3 3 3z'/%3E%3C/svg%3E"); +} +.action-link--icon-show:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%2300309e' fill-rule='evenodd'%3E%3Cpath d='m8 3c-3.6363601 0-6.7418187 2.0733333-8 5 1.2581813 2.926667 4.3636399 5 8 5 3.63636 0 6.741866-2.073333 8-5-1.258134-2.9266667-4.36364-5-8-5zm0 8c1.6568531 0 3-1.343147 3-3 0-1.6568534-1.3431469-3-3-3-1.6568535 0-3 1.3431466-3 3 0 1.656853 1.3431466 3 3 3z'/%3E%3C/svg%3E"); +} + +/* Show - danger */ +.action-link--icon-show.action-link--danger::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23d72222' fill-rule='evenodd'%3E%3Cpath d='m8 3c-3.6363601 0-6.7418187 2.0733333-8 5 1.2581813 2.926667 4.3636399 5 8 5 3.63636 0 6.741866-2.073333 8-5-1.258134-2.9266667-4.36364-5-8-5zm0 8c1.6568531 0 3-1.343147 3-3 0-1.6568534-1.3431469-3-3-3-1.6568535 0-3 1.3431466-3 3 0 1.656853 1.3431466 3 3 3z'/%3E%3C/svg%3E"); +} +.action-link--icon-show.action-link--danger:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23c11f1f' fill-rule='evenodd'%3E%3Cpath d='m8 3c-3.6363601 0-6.7418187 2.0733333-8 5 1.2581813 2.926667 4.3636399 5 8 5 3.63636 0 6.741866-2.073333 8-5-1.258134-2.9266667-4.36364-5-8-5zm0 8c1.6568531 0 3-1.343147 3-3 0-1.6568534-1.3431469-3-3-3-1.6568535 0-3 1.3431466-3 3 0 1.656853 1.3431466 3 3 3z'/%3E%3C/svg%3E"); +} +.action-link--icon-show.action-link--danger:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23ab1b1b' fill-rule='evenodd'%3E%3Cpath d='m8 3c-3.6363601 0-6.7418187 2.0733333-8 5 1.2581813 2.926667 4.3636399 5 8 5 3.63636 0 6.741866-2.073333 8-5-1.258134-2.9266667-4.36364-5-8-5zm0 8c1.6568531 0 3-1.343147 3-3 0-1.6568534-1.3431469-3-3-3-1.6568535 0-3 1.3431466-3 3 0 1.656853 1.3431466 3 3 3z'/%3E%3C/svg%3E"); +} + +@media screen and (-ms-high-contrast: active) { + .action-link--icon-show.action-link--icon-show.action-link--icon-show::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='windowText' fill-rule='evenodd'%3E%3Cpath d='m8 3c-3.6363601 0-6.7418187 2.0733333-8 5 1.2581813 2.926667 4.3636399 5 8 5 3.63636 0 6.741866-2.073333 8-5-1.258134-2.9266667-4.36364-5-8-5zm0 8c1.6568531 0 3-1.343147 3-3 0-1.6568534-1.3431469-3-3-3-1.6568535 0-3 1.3431466-3 3 0 1.656853 1.3431466 3 3 3z'/%3E%3C/svg%3E") !important; + } +} + +/* Hide */ +.action-link--icon-hide::before { + content: ""; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23545560' fill-rule='evenodd'%3E%3Cpath d='m2.0106399 1.6964404-.0106399.0106426 12.072 12.07205-.6964.696467-2.077627-2.077613c-1.015347.38784-2.1292399.602013-3.297973.602013-3.6363601 0-6.7418187-2.073333-8-5 .64703865-1.5050798 1.7826266-2.7844797 3.2277199-3.6722797l-2.2277199-2.2277203.7071066-.707096zm2.98936 6.3035598c0-.54608.1459066-1.0580666.4008533-1.4991333l4.0982932 4.0982801c-.4410666.25496-.9530666.400853-1.4991464.400853-1.6568535 0-3-1.343146-3-3z'/%3E%3Cpath d='m5.1510932 3.4439603 1.75984 1.75984c.3376-.1315867.7048933-.2038 1.0890666-.2038 1.6568533-.0000003 3.0000002 1.3431466 3.0000002 2.9999997 0 .3841735-.07221.7514668-.2038 1.0890668l2.344093 2.3440932c1.269973-.871746 2.26864-2.0582932 2.859707-3.43316-1.258134-2.9266664-4.36364-5-8-5-.9987733 0-1.9575066.1564134-2.8489066.44396z'/%3E%3C/svg%3E"); +} +.action-link--icon-hide:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%230036b1' fill-rule='evenodd'%3E%3Cpath d='m2.0106399 1.6964404-.0106399.0106426 12.072 12.07205-.6964.696467-2.077627-2.077613c-1.015347.38784-2.1292399.602013-3.297973.602013-3.6363601 0-6.7418187-2.073333-8-5 .64703865-1.5050798 1.7826266-2.7844797 3.2277199-3.6722797l-2.2277199-2.2277203.7071066-.707096zm2.98936 6.3035598c0-.54608.1459066-1.0580666.4008533-1.4991333l4.0982932 4.0982801c-.4410666.25496-.9530666.400853-1.4991464.400853-1.6568535 0-3-1.343146-3-3z'/%3E%3Cpath d='m5.1510932 3.4439603 1.75984 1.75984c.3376-.1315867.7048933-.2038 1.0890666-.2038 1.6568533-.0000003 3.0000002 1.3431466 3.0000002 2.9999997 0 .3841735-.07221.7514668-.2038 1.0890668l2.344093 2.3440932c1.269973-.871746 2.26864-2.0582932 2.859707-3.43316-1.258134-2.9266664-4.36364-5-8-5-.9987733 0-1.9575066.1564134-2.8489066.44396z'/%3E%3C/svg%3E"); +} +.action-link--icon-hide:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%2300309e' fill-rule='evenodd'%3E%3Cpath d='m2.0106399 1.6964404-.0106399.0106426 12.072 12.07205-.6964.696467-2.077627-2.077613c-1.015347.38784-2.1292399.602013-3.297973.602013-3.6363601 0-6.7418187-2.073333-8-5 .64703865-1.5050798 1.7826266-2.7844797 3.2277199-3.6722797l-2.2277199-2.2277203.7071066-.707096zm2.98936 6.3035598c0-.54608.1459066-1.0580666.4008533-1.4991333l4.0982932 4.0982801c-.4410666.25496-.9530666.400853-1.4991464.400853-1.6568535 0-3-1.343146-3-3z'/%3E%3Cpath d='m5.1510932 3.4439603 1.75984 1.75984c.3376-.1315867.7048933-.2038 1.0890666-.2038 1.6568533-.0000003 3.0000002 1.3431466 3.0000002 2.9999997 0 .3841735-.07221.7514668-.2038 1.0890668l2.344093 2.3440932c1.269973-.871746 2.26864-2.0582932 2.859707-3.43316-1.258134-2.9266664-4.36364-5-8-5-.9987733 0-1.9575066.1564134-2.8489066.44396z'/%3E%3C/svg%3E"); +} + +/* Hide - danger */ +.action-link--icon-hide.action-link--danger::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23d72222' fill-rule='evenodd'%3E%3Cpath d='m2.0106399 1.6964404-.0106399.0106426 12.072 12.07205-.6964.696467-2.077627-2.077613c-1.015347.38784-2.1292399.602013-3.297973.602013-3.6363601 0-6.7418187-2.073333-8-5 .64703865-1.5050798 1.7826266-2.7844797 3.2277199-3.6722797l-2.2277199-2.2277203.7071066-.707096zm2.98936 6.3035598c0-.54608.1459066-1.0580666.4008533-1.4991333l4.0982932 4.0982801c-.4410666.25496-.9530666.400853-1.4991464.400853-1.6568535 0-3-1.343146-3-3z'/%3E%3Cpath d='m5.1510932 3.4439603 1.75984 1.75984c.3376-.1315867.7048933-.2038 1.0890666-.2038 1.6568533-.0000003 3.0000002 1.3431466 3.0000002 2.9999997 0 .3841735-.07221.7514668-.2038 1.0890668l2.344093 2.3440932c1.269973-.871746 2.26864-2.0582932 2.859707-3.43316-1.258134-2.9266664-4.36364-5-8-5-.9987733 0-1.9575066.1564134-2.8489066.44396z'/%3E%3C/svg%3E"); +} +.action-link--icon-hide.action-link--danger:hover::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23c11f1f' fill-rule='evenodd'%3E%3Cpath d='m2.0106399 1.6964404-.0106399.0106426 12.072 12.07205-.6964.696467-2.077627-2.077613c-1.015347.38784-2.1292399.602013-3.297973.602013-3.6363601 0-6.7418187-2.073333-8-5 .64703865-1.5050798 1.7826266-2.7844797 3.2277199-3.6722797l-2.2277199-2.2277203.7071066-.707096zm2.98936 6.3035598c0-.54608.1459066-1.0580666.4008533-1.4991333l4.0982932 4.0982801c-.4410666.25496-.9530666.400853-1.4991464.400853-1.6568535 0-3-1.343146-3-3z'/%3E%3Cpath d='m5.1510932 3.4439603 1.75984 1.75984c.3376-.1315867.7048933-.2038 1.0890666-.2038 1.6568533-.0000003 3.0000002 1.3431466 3.0000002 2.9999997 0 .3841735-.07221.7514668-.2038 1.0890668l2.344093 2.3440932c1.269973-.871746 2.26864-2.0582932 2.859707-3.43316-1.258134-2.9266664-4.36364-5-8-5-.9987733 0-1.9575066.1564134-2.8489066.44396z'/%3E%3C/svg%3E"); +} +.action-link--icon-hide.action-link--danger:active::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='%23ab1b1b' fill-rule='evenodd'%3E%3Cpath d='m2.0106399 1.6964404-.0106399.0106426 12.072 12.07205-.6964.696467-2.077627-2.077613c-1.015347.38784-2.1292399.602013-3.297973.602013-3.6363601 0-6.7418187-2.073333-8-5 .64703865-1.5050798 1.7826266-2.7844797 3.2277199-3.6722797l-2.2277199-2.2277203.7071066-.707096zm2.98936 6.3035598c0-.54608.1459066-1.0580666.4008533-1.4991333l4.0982932 4.0982801c-.4410666.25496-.9530666.400853-1.4991464.400853-1.6568535 0-3-1.343146-3-3z'/%3E%3Cpath d='m5.1510932 3.4439603 1.75984 1.75984c.3376-.1315867.7048933-.2038 1.0890666-.2038 1.6568533-.0000003 3.0000002 1.3431466 3.0000002 2.9999997 0 .3841735-.07221.7514668-.2038 1.0890668l2.344093 2.3440932c1.269973-.871746 2.26864-2.0582932 2.859707-3.43316-1.258134-2.9266664-4.36364-5-8-5-.9987733 0-1.9575066.1564134-2.8489066.44396z'/%3E%3C/svg%3E"); +} + +@media screen and (-ms-high-contrast: active) { + .action-link--icon-hide.action-link--icon-hide.action-link--icon-hide::before { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='windowText' fill-rule='evenodd'%3E%3Cpath d='m2.0106399 1.6964404-.0106399.0106426 12.072 12.07205-.6964.696467-2.077627-2.077613c-1.015347.38784-2.1292399.602013-3.297973.602013-3.6363601 0-6.7418187-2.073333-8-5 .64703865-1.5050798 1.7826266-2.7844797 3.2277199-3.6722797l-2.2277199-2.2277203.7071066-.707096zm2.98936 6.3035598c0-.54608.1459066-1.0580666.4008533-1.4991333l4.0982932 4.0982801c-.4410666.25496-.9530666.400853-1.4991464.400853-1.6568535 0-3-1.343146-3-3z'/%3E%3Cpath d='m5.1510932 3.4439603 1.75984 1.75984c.3376-.1315867.7048933-.2038 1.0890666-.2038 1.6568533-.0000003 3.0000002 1.3431466 3.0000002 2.9999997 0 .3841735-.07221.7514668-.2038 1.0890668l2.344093 2.3440932c1.269973-.871746 2.26864-2.0582932 2.859707-3.43316-1.258134-2.9266664-4.36364-5-8-5-.9987733 0-1.9575066.1564134-2.8489066.44396z'/%3E%3C/svg%3E"); + } +} diff --git a/core/themes/claro/css/src/components/ajax-progress.module.css b/core/themes/claro/css/src/components/ajax-progress.module.css new file mode 100644 index 0000000000..f9f09fbadc --- /dev/null +++ b/core/themes/claro/css/src/components/ajax-progress.module.css @@ -0,0 +1,238 @@ +/* + * DO NOT EDIT THIS FILE. + * See the following change record for more information, + * https://www.drupal.org/node/2815083 + * @preserve + */ + +/** + * @file + * Throbber. + */ + +:root { + /* + * Color Palette. + */ + /* Secondary. */ + /* Variations. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 10% darker than base. */ /* 20% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ + /* + * Base. + */ + /* + * Typography. + */ /* 1rem = 16px if font root is 100% ands browser defaults are used. */ /* ~32px */ /* ~29px */ /* ~26px */ /* ~23px */ /* ~20px */ /* 18px */ /* ~14px */ /* ~13px */ /* ~11px */ + /** + * Spaces. + */ /* 3 * 16px = 48px */ /* 1.5 * 16px = 24px */ /* 1 * 16px = 16px */ /* 0.75 * 16px = 12px */ /* 0.5 * 16px = 8px */ + /* + * Common. + */ + /* + * Inputs. + */ /* Absolute zero with opacity. */ /* Davy's grey with 0.6 opacity. */ /* Light gray with 0.3 opacity on white bg. */ /* Old silver with 0.5 opacity on white bg. */ /* (1/8)em ~ 2px */ /* (1/16)em ~ 1px */ /* Font size is too big to use 1rem for extrasmall line-height */ /* 7px inside the form element label. */ /* 8px with the checkbox width of 19px */ + /* + * Details. + */ + /** + * Buttons. + */ + /** + * jQuery.UI dropdown. + */ /* Light gray with 0.8 opacity. */ /* Text color with 0.1 opacity. */ + /** + * Progress bar. + */ + /** + * Tabledrag icon size. + */ /* 17px */ + /** + * Ajax progress. + */ + /** + * Breadcrumb. + */ +} + +.ajax-progress { + display: inline-block; +} + +/** + * Progress bar. + */ + +.ajax-progress-bar { + width: 13em; + padding: 0 0.3125rem; /* 0 5px */ +} + +/** + * Throbber. + */ + +.ajax-progress--throbber { + position: relative; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -moz-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + -webkit-align-content: center; + -ms-flex-line-pack: center; + align-content: center; + height: 1.125rem; + margin: -3px 0.75rem 0; + vertical-align: middle; + white-space: nowrap; + line-height: 1.125rem; +} + +/** + * Remove margin from ajax throbbers following buttons because buttons already + * have a large margin set. + */ + +.js .button:not(.js-hide) + .ajax-progress--throbber { + margin-left: 0; /* LTR */ +} + +[dir="rtl"].js .button:not(.js-hide) + .ajax-progress--throbber { + margin-right: 0; + margin-left: 0.75rem; +} + +/** + * Restore start margin for ajax throbbers inside a managed file widget. + */ + +.js .form-type--managed-file .button:not(.js-hide) + .ajax-progress--throbber, +.js .file-operations-cell .button:not(.js-hide) + .ajax-progress--throbber { + margin-left: 0.75rem; /* LTR */ +} + +[dir="rtl"].js .form-type--managed-file .button:not(.js-hide) + .ajax-progress--throbber, +[dir="rtl"].js .file-operations-cell .button:not(.js-hide) + .ajax-progress--throbber { + margin-right: 0.75rem; + margin-left: 0.75rem; +} + +.ajax-progress__throbber { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 1.125rem; + height: 1.125rem; + -webkit-animation: claro-throbber 0.75s linear infinite; + -moz-animation: claro-throbber 0.75s linear infinite; + -o-animation: claro-throbber 0.75s linear infinite; + animation: claro-throbber 0.75s linear infinite; + border: 2px solid #003cc5; + border-right: 2px dotted transparent; + border-radius: 50%; +} + +.ajax-progress__message { + display: inline-block; + padding-left: 0.75rem; + font-size: 0.889rem; +} + +[dir="rtl"] .ajax-progress__message { + padding-right: 0.75rem; + padding-left: 0; +} + +/** + * Full screen throbber. + */ + +.ajax-progress--fullscreen { + position: fixed; + z-index: 1000; + top: 50%; + left: 50%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 3.5rem; /* 56px */ + height: 3.5rem; + margin: -1.75rem; + border: 1px solid rgba(216, 217, 224, 0.8); + border-radius: 3.5rem; + background: #fff; + -webkit-box-shadow: 0 0.25rem 0.625rem rgba(34, 35, 48, 0.1); + box-shadow: 0 0.25rem 0.625rem rgba(34, 35, 48, 0.1); +} + +.ajax-progress__throbber--fullscreen { + position: absolute; + top: 50%; + left: 50%; + width: 1.75rem; /* 28px */ + height: 1.75rem; /* 28px */ + margin: -0.875rem; + content: ""; + border: 3px solid #003cc5; + border-right: 3px dotted transparent; +} + +@media screen and (-ms-high-contrast: active) { + /** + * Throbber animation is shaky on Edge RTL on high contrast for border width + * less than 4px. + */ + @supports (-ms-ime-align:auto) { + [dir="rtl"] .ajax-progress__throbber { + border-width: 4px; + } + } +} + +@-webkit-keyframes claro-throbber { + 0% { + -webkit-transform: rotateZ(0); + transform: rotateZ(0); + } + 100% { + -webkit-transform: rotateZ(360deg); + transform: rotateZ(360deg); + } +} + +@-moz-keyframes claro-throbber { + 0% { + -moz-transform: rotateZ(0); + transform: rotateZ(0); + } + 100% { + -moz-transform: rotateZ(360deg); + transform: rotateZ(360deg); + } +} + +@-o-keyframes claro-throbber { + 0% { + -o-transform: rotateZ(0); + transform: rotateZ(0); + } + 100% { + -o-transform: rotateZ(360deg); + transform: rotateZ(360deg); + } +} + +@keyframes claro-throbber { + 0% { + -webkit-transform: rotateZ(0); + -moz-transform: rotateZ(0); + -o-transform: rotateZ(0); + transform: rotateZ(0); + } + 100% { + -webkit-transform: rotateZ(360deg); + -moz-transform: rotateZ(360deg); + -o-transform: rotateZ(360deg); + transform: rotateZ(360deg); + } +} diff --git a/core/themes/claro/css/src/components/ajax-progress.module.pcss.css b/core/themes/claro/css/src/components/ajax-progress.module.pcss.css new file mode 100644 index 0000000000..91a595f394 --- /dev/null +++ b/core/themes/claro/css/src/components/ajax-progress.module.pcss.css @@ -0,0 +1,127 @@ +/** + * @file + * Throbber. + */ + +@import "../base/variables.pcss.css"; + +.ajax-progress { + display: inline-block; +} + +/** + * Progress bar. + */ + +.ajax-progress-bar { + width: 13em; + padding: 0 0.3125rem; /* 0 5px */ +} + +/** + * Throbber. + */ +.ajax-progress--throbber { + position: relative; + display: inline-flex; + align-content: center; + height: 1.125rem; + margin: -3px var(--ajax-progress-margin-horizontal) 0; + vertical-align: middle; + white-space: nowrap; + line-height: 1.125rem; +} + +/** + * Remove margin from ajax throbbers following buttons because buttons already + * have a large margin set. + */ +.js .button:not(.js-hide) + .ajax-progress--throbber { + margin-left: 0; /* LTR */ +} +[dir="rtl"].js .button:not(.js-hide) + .ajax-progress--throbber { + margin-right: 0; + margin-left: var(--ajax-progress-margin-horizontal); +} + +/** + * Restore start margin for ajax throbbers inside a managed file widget. + */ +.js .form-type--managed-file .button:not(.js-hide) + .ajax-progress--throbber, +.js .file-operations-cell .button:not(.js-hide) + .ajax-progress--throbber { + margin-left: var(--ajax-progress-margin-horizontal); /* LTR */ +} +[dir="rtl"].js .form-type--managed-file .button:not(.js-hide) + .ajax-progress--throbber, +[dir="rtl"].js .file-operations-cell .button:not(.js-hide) + .ajax-progress--throbber { + margin-right: var(--ajax-progress-margin-horizontal); + margin-left: var(--ajax-progress-margin-horizontal); +} + +.ajax-progress__throbber { + box-sizing: border-box; + width: 1.125rem; + height: 1.125rem; + animation: claro-throbber 0.75s linear infinite; + border: 2px solid var(--color-absolutezero); + border-right: 2px dotted transparent; + border-radius: 50%; +} + +.ajax-progress__message { + display: inline-block; + padding-left: var(--ajax-progress-margin-horizontal); + font-size: var(--font-size-label); +} +[dir="rtl"] .ajax-progress__message { + padding-right: var(--ajax-progress-margin-horizontal); + padding-left: 0; +} +/** + * Full screen throbber. + */ +.ajax-progress--fullscreen { + position: fixed; + z-index: 1000; + top: 50%; + left: 50%; + box-sizing: border-box; + width: 3.5rem; /* 56px */ + height: 3.5rem; + margin: -1.75rem; + border: var(--input-border-size) solid var(--jui-dropdown-border-color); + border-radius: 3.5rem; + background: var(--color-white); + box-shadow: 0 0.25rem 0.625rem var(--jui-dropdown-shadow-color); +} +.ajax-progress__throbber--fullscreen { + position: absolute; + top: 50%; + left: 50%; + width: 1.75rem; /* 28px */ + height: 1.75rem; /* 28px */ + margin: -0.875rem; + content: ""; + border: 3px solid var(--color-absolutezero); + border-right: 3px dotted transparent; +} + +@media screen and (-ms-high-contrast: active) { + /** + * Throbber animation is shaky on Edge RTL on high contrast for border width + * less than 4px. + */ + @supports (-ms-ime-align:auto) { + [dir="rtl"] .ajax-progress__throbber { + border-width: 4px; + } + } +} + +@keyframes claro-throbber { + 0% { + transform: rotateZ(0); + } + 100% { + transform: rotateZ(360deg); + } +} diff --git a/core/themes/claro/css/src/components/autocomplete-loading.module.css b/core/themes/claro/css/src/components/autocomplete-loading.module.css new file mode 100644 index 0000000000..dde906a455 --- /dev/null +++ b/core/themes/claro/css/src/components/autocomplete-loading.module.css @@ -0,0 +1,148 @@ +/* + * DO NOT EDIT THIS FILE. + * See the following change record for more information, + * https://www.drupal.org/node/2815083 + * @preserve + */ + +/** + * @file + * Visual styles for animated throbber. + * + * @see autocomplete.js + */ + +:root { + /* + * Color Palette. + */ + /* Secondary. */ + /* Variations. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 10% darker than base. */ /* 20% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ + /* + * Base. + */ + /* + * Typography. + */ /* 1rem = 16px if font root is 100% ands browser defaults are used. */ /* ~32px */ /* ~29px */ /* ~26px */ /* ~23px */ /* ~20px */ /* 18px */ /* ~14px */ /* ~13px */ /* ~11px */ + /** + * Spaces. + */ /* 3 * 16px = 48px */ /* 1.5 * 16px = 24px */ /* 1 * 16px = 16px */ /* 0.75 * 16px = 12px */ /* 0.5 * 16px = 8px */ + /* + * Common. + */ + /* + * Inputs. + */ /* Absolute zero with opacity. */ /* Davy's grey with 0.6 opacity. */ /* Light gray with 0.3 opacity on white bg. */ /* Old silver with 0.5 opacity on white bg. */ /* (1/8)em ~ 2px */ /* (1/16)em ~ 1px */ /* Font size is too big to use 1rem for extrasmall line-height */ /* 7px inside the form element label. */ /* 8px with the checkbox width of 19px */ + /* + * Details. + */ + /** + * Buttons. + */ + /** + * jQuery.UI dropdown. + */ /* Light gray with 0.8 opacity. */ /* Text color with 0.1 opacity. */ + /** + * Progress bar. + */ + /** + * Tabledrag icon size. + */ /* 17px */ + /** + * Ajax progress. + */ + /** + * Breadcrumb. + */ +} + +/** + * Since the autocomplete library is attached conditionally and not globally, + * we can be 99% sure that the default icon will be used. + * With inline SVGs we can prevent a HTTP request and repaint addressing the + * autocomplete input's background — until are sure that it will be pushed by + * the server with HTTP/2. + * + * The autocompleting (active) state's background-image is inlined because + * non-used CSS selectors are usually ignored; popular browsers don't download + * their 'url' references. + * If these selectors become active, the browser needs some time for painting + * previously ignored remote asset: it should get it from server, parse it and + * repaint the background of autocomplete field. With the inlined background we + * can prevent an additional timeout caused by a new request/response pair. + * Besides this, the autocompleting event itself may easily finish before the + * missing asset gets downloaded/parsed/painted, and the missing instant visual + * feedback would be a usability/accessibility issue as well. + */ + +.js .form-autocomplete { + background-image: url("data:image/svg+xml,%3Csvg width='40' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m8,0.999781c-4.5394538,-0.1723607 -8.18800628,4.7870352 -6.6873554,9.068641 1.1767997,4.383903 6.9938335,6.416563 10.6372244,3.700244 0.325764,-0.391006 0.56541,0.275384 0.84585,0.440896 1.246479,1.246479 2.492958,2.492959 3.739437,3.739438 0.471354,-0.471354 0.942709,-0.942709 1.414063,-1.414063 -1.44987,-1.44987 -2.89974,-2.899739 -4.34961,-4.349609C16.410345,8.7174615 14.748115,2.9379071 10.536504,1.4755074 9.7302231,1.1615612 8.8650587,0.99941873 8,0.999781Z m0,2c3.242467,-0.1231148 5.848576,3.4193109 4.776682,6.477601 -0.841211,3.131959 -4.9939918,4.58038 -7.5998944,2.649077C2.4322236,10.397214 2.2765833,6.0022025 4.8919502,4.0831465 5.7667487,3.38528 6.8811016,2.996997 8,2.999781Z' fill='%23868686' /%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: 100% 50%; +} + +.js .form-autocomplete::-ms-clear { + display: none; +} + +.js[dir="rtl"] .form-autocomplete { + background-image: url("data:image/svg+xml,%3Csvg width='40' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12,0.999781c4.539454,-0.1723607 8.188006,4.7870352 6.687355,9.068641 -1.176799,4.383903 -6.993833,6.416563 -10.637224,3.700244C7.724367,13.37766 7.484721,14.04405 7.204281,14.209562 5.957802,15.456041 4.711323,16.702521 3.464844,17.949 2.99349,17.477646 2.522135,17.006291 2.050781,16.534937 3.500651,15.085067 4.950521,13.635198 6.400391,12.185328 3.589655,8.7174615 5.251885,2.9379071 9.463496,1.4755074 10.269777,1.1615612 11.134941,0.99941873 12,0.999781Z m0,2C8.757533,2.8766662 6.151424,6.4190919 7.223318,9.477382c0.841211,3.131959 4.993992,4.58038 7.599894,2.649077C17.567776,10.397214 17.723417,6.0022025 15.10805,4.0831465 14.233251,3.38528 13.118898,2.996997 12,2.999781Z' fill='%23868686' /%3E%3C/svg%3E"); + background-position: 0 50%; +} + +.js .form-autocomplete.is-autocompleting { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' height='20' width='40'%3E%3Cstyle type='text/css'%3E @keyframes s%7B0%25%7Btransform:rotate(0deg) translate(-50%25,-50%25)%7D50%25%7Btransform:rotate(430deg) translate(-50%25,-50%25);stroke-dashoffset:20%7D100%25%7Btransform:rotate(720deg) translate(-50%25,-50%25)%7D%7Dellipse%7Banimation:s 1s linear infinite%7D%3C/style%3E%3Cg transform='translate(5 5)'%3E%3Cellipse fill='none' ry='4' rx='4' cy='5' cx='5' stroke='%23004adc' stroke-width='1' stroke-dashoffset='6.125' stroke-dasharray='25' transform='translate(-5 -5)' /%3E%3C/g%3E%3C/svg%3E"); +} + +.js[dir="rtl"] .form-autocomplete.is-autocompleting { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' height='20' width='40'%3E%3Cstyle type='text/css'%3E @keyframes s%7B0%25%7Btransform:rotate(0deg) translate(-50%25,-50%25)%7D50%25%7Btransform:rotate(-430deg) translate(-50%25,-50%25);stroke-dashoffset:20%7D100%25%7Btransform:rotate(-720deg) translate(-50%25,-50%25)%7D%7Dellipse%7Banimation:s 1s linear infinite%7D%3C/style%3E%3Cg transform='translate(5 5)'%3E%3Cellipse fill='none' ry='4' rx='4' cy='5' cx='5' stroke='%23004adc' stroke-width='1' stroke-dashoffset='6.125' stroke-dasharray='25' transform='translate(-5 -5)' /%3E%3C/g%3E%3C/svg%3E"); +} + +/* IE11 does not animate inline SVG. */ + +/* stylelint-disable-next-line selector-type-no-unknown */ + +_:-ms-fullscreen, +.js .form-autocomplete.is-autocompleting { + background-image: url("../../../images/spinner-ltr.gif"); + background-size: 2.5rem 1.25rem; +} + +/* stylelint-disable-next-line selector-type-no-unknown */ + +_:-ms-fullscreen, +.js[dir="rtl"] .form-autocomplete.is-autocompleting { + background-image: url("../../../images/spinner-rtl.gif"); +} + +/** + * Autocomplete wrapper for autocompleting message. + */ + +.claro-autocomplete { + position: relative; + display: inline-block; + max-width: 100%; + margin: 0.25rem 0; +} + +.claro-autocomplete__message { + position: absolute; + right: 0; + bottom: 100%; + max-width: 100%; + margin-bottom: 0.15rem; + color: #003cc5; + font-size: 0.702rem; /* ~11px */ + font-weight: bold; + line-height: 1.125rem; /* 18px */ +} + +[dir="rtl"] .claro-autocomplete__message { + right: auto; + left: 0; +} + +.js .is-autocompleting + .claro-autocomplete__message { + display: block; +} diff --git a/core/themes/claro/css/src/components/autocomplete-loading.module.pcss.css b/core/themes/claro/css/src/components/autocomplete-loading.module.pcss.css new file mode 100644 index 0000000000..828fe2b2a4 --- /dev/null +++ b/core/themes/claro/css/src/components/autocomplete-loading.module.pcss.css @@ -0,0 +1,86 @@ +/** + * @file + * Visual styles for animated throbber. + * + * @see autocomplete.js + */ + +@import "../base/variables.pcss.css"; + +/** + * Since the autocomplete library is attached conditionally and not globally, + * we can be 99% sure that the default icon will be used. + * With inline SVGs we can prevent a HTTP request and repaint addressing the + * autocomplete input's background — until are sure that it will be pushed by + * the server with HTTP/2. + * + * The autocompleting (active) state's background-image is inlined because + * non-used CSS selectors are usually ignored; popular browsers don't download + * their 'url' references. + * If these selectors become active, the browser needs some time for painting + * previously ignored remote asset: it should get it from server, parse it and + * repaint the background of autocomplete field. With the inlined background we + * can prevent an additional timeout caused by a new request/response pair. + * Besides this, the autocompleting event itself may easily finish before the + * missing asset gets downloaded/parsed/painted, and the missing instant visual + * feedback would be a usability/accessibility issue as well. + */ + +.js .form-autocomplete { + background-image: url("data:image/svg+xml,%3Csvg width='40' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m8,0.999781c-4.5394538,-0.1723607 -8.18800628,4.7870352 -6.6873554,9.068641 1.1767997,4.383903 6.9938335,6.416563 10.6372244,3.700244 0.325764,-0.391006 0.56541,0.275384 0.84585,0.440896 1.246479,1.246479 2.492958,2.492959 3.739437,3.739438 0.471354,-0.471354 0.942709,-0.942709 1.414063,-1.414063 -1.44987,-1.44987 -2.89974,-2.899739 -4.34961,-4.349609C16.410345,8.7174615 14.748115,2.9379071 10.536504,1.4755074 9.7302231,1.1615612 8.8650587,0.99941873 8,0.999781Z m0,2c3.242467,-0.1231148 5.848576,3.4193109 4.776682,6.477601 -0.841211,3.131959 -4.9939918,4.58038 -7.5998944,2.649077C2.4322236,10.397214 2.2765833,6.0022025 4.8919502,4.0831465 5.7667487,3.38528 6.8811016,2.996997 8,2.999781Z' fill='%23868686' /%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: 100% 50%; +} +.js .form-autocomplete::-ms-clear { + display: none; +} +.js[dir="rtl"] .form-autocomplete { + background-image: url("data:image/svg+xml,%3Csvg width='40' height='20' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12,0.999781c4.539454,-0.1723607 8.188006,4.7870352 6.687355,9.068641 -1.176799,4.383903 -6.993833,6.416563 -10.637224,3.700244C7.724367,13.37766 7.484721,14.04405 7.204281,14.209562 5.957802,15.456041 4.711323,16.702521 3.464844,17.949 2.99349,17.477646 2.522135,17.006291 2.050781,16.534937 3.500651,15.085067 4.950521,13.635198 6.400391,12.185328 3.589655,8.7174615 5.251885,2.9379071 9.463496,1.4755074 10.269777,1.1615612 11.134941,0.99941873 12,0.999781Z m0,2C8.757533,2.8766662 6.151424,6.4190919 7.223318,9.477382c0.841211,3.131959 4.993992,4.58038 7.599894,2.649077C17.567776,10.397214 17.723417,6.0022025 15.10805,4.0831465 14.233251,3.38528 13.118898,2.996997 12,2.999781Z' fill='%23868686' /%3E%3C/svg%3E"); + background-position: 0 50%; +} +.js .form-autocomplete.is-autocompleting { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' height='20' width='40'%3E%3Cstyle type='text/css'%3E @keyframes s%7B0%25%7Btransform:rotate(0deg) translate(-50%25,-50%25)%7D50%25%7Btransform:rotate(430deg) translate(-50%25,-50%25);stroke-dashoffset:20%7D100%25%7Btransform:rotate(720deg) translate(-50%25,-50%25)%7D%7Dellipse%7Banimation:s 1s linear infinite%7D%3C/style%3E%3Cg transform='translate(5 5)'%3E%3Cellipse fill='none' ry='4' rx='4' cy='5' cx='5' stroke='%23004adc' stroke-width='1' stroke-dashoffset='6.125' stroke-dasharray='25' transform='translate(-5 -5)' /%3E%3C/g%3E%3C/svg%3E"); +} +.js[dir="rtl"] .form-autocomplete.is-autocompleting { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' height='20' width='40'%3E%3Cstyle type='text/css'%3E @keyframes s%7B0%25%7Btransform:rotate(0deg) translate(-50%25,-50%25)%7D50%25%7Btransform:rotate(-430deg) translate(-50%25,-50%25);stroke-dashoffset:20%7D100%25%7Btransform:rotate(-720deg) translate(-50%25,-50%25)%7D%7Dellipse%7Banimation:s 1s linear infinite%7D%3C/style%3E%3Cg transform='translate(5 5)'%3E%3Cellipse fill='none' ry='4' rx='4' cy='5' cx='5' stroke='%23004adc' stroke-width='1' stroke-dashoffset='6.125' stroke-dasharray='25' transform='translate(-5 -5)' /%3E%3C/g%3E%3C/svg%3E"); +} +/* IE11 does not animate inline SVG. */ +/* stylelint-disable-next-line selector-type-no-unknown */ +_:-ms-fullscreen, +.js .form-autocomplete.is-autocompleting { + background-image: url("../../../images/spinner-ltr.gif"); + background-size: 2.5rem 1.25rem; +} +/* stylelint-disable-next-line selector-type-no-unknown */ +_:-ms-fullscreen, +.js[dir="rtl"] .form-autocomplete.is-autocompleting { + background-image: url("../../../images/spinner-rtl.gif"); +} + +/** + * Autocomplete wrapper for autocompleting message. + */ +.claro-autocomplete { + position: relative; + display: inline-block; + max-width: 100%; + margin: 0.25rem 0; +} +.claro-autocomplete__message { + position: absolute; + right: 0; + bottom: 100%; + max-width: 100%; + margin-bottom: 0.15rem; + color: var(--color-link); + font-size: var(--font-size-xxs); /* ~11px */ + font-weight: bold; + line-height: calc(18rem / 16); /* 18px */ +} +[dir="rtl"] .claro-autocomplete__message { + right: auto; + left: 0; +} +.js .is-autocompleting + .claro-autocomplete__message { + display: block; +} diff --git a/core/themes/claro/css/src/components/breadcrumb.css b/core/themes/claro/css/src/components/breadcrumb.css new file mode 100644 index 0000000000..b496acca67 --- /dev/null +++ b/core/themes/claro/css/src/components/breadcrumb.css @@ -0,0 +1,90 @@ +/* + * DO NOT EDIT THIS FILE. + * See the following change record for more information, + * https://www.drupal.org/node/2815083 + * @preserve + */ + +/** + * @file + * Breadcrumbs. + */ + +:root { + /* + * Color Palette. + */ + /* Secondary. */ + /* Variations. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 10% darker than base. */ /* 20% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ /* 5% darker than base. */ /* 10% darker than base. */ + /* + * Base. + */ + /* + * Typography. + */ /* 1rem = 16px if font root is 100% ands browser defaults are used. */ /* ~32px */ /* ~29px */ /* ~26px */ /* ~23px */ /* ~20px */ /* 18px */ /* ~14px */ /* ~13px */ /* ~11px */ + /** + * Spaces. + */ /* 3 * 16px = 48px */ /* 1.5 * 16px = 24px */ /* 1 * 16px = 16px */ /* 0.75 * 16px = 12px */ /* 0.5 * 16px = 8px */ + /* + * Common. + */ + /* + * Inputs. + */ /* Absolute zero with opacity. */ /* Davy's grey with 0.6 opacity. */ /* Light gray with 0.3 opacity on white bg. */ /* Old silver with 0.5 opacity on white bg. */ /* (1/8)em ~ 2px */ /* (1/16)em ~ 1px */ /* Font size is too big to use 1rem for extrasmall line-height */ /* 7px inside the form element label. */ /* 8px with the checkbox width of 19px */ + /* + * Details. + */ + /** + * Buttons. + */ + /** + * jQuery.UI dropdown. + */ /* Light gray with 0.8 opacity. */ /* Text color with 0.1 opacity. */ + /** + * Progress bar. + */ + /** + * Tabledrag icon size. + */ /* 17px */ + /** + * Ajax progress. + */ + /** + * Breadcrumb. + */ +} + +.breadcrumb { + padding: 0; + color: #222330; + font-size: 0.79rem; +} + +.breadcrumb__list, +[dir="rtl"] .breadcrumb__list { + margin: 0; + padding: 0; + list-style-type: none; +} + +.breadcrumb__item, +.breadcrumb__link { + display: inline; + text-decoration: none; + color: #222330; + font-weight: bold; +} + +.breadcrumb__item + .breadcrumb__item::before { + padding: 0 0.75rem; + content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 5 8' height='8' width='5'%3E%3Cpath d='M1.2070312,0.64696878 0.5,1.354 3.1464844,4.0004844 0.5,6.6469688 1.2070312,7.354 4.5605468,4.0004844Z' fill='%23545560'/%3E%3C/svg%3E"); +} + +[dir="rtl"] .breadcrumb__item + .breadcrumb__item::before { + content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='5' height='8' viewBox='0 0 5 8'%3E%3Cpath d='M3.7929688,0.64696878 4.5,1.354 1.8535156,4.0004844 4.5,6.6469688 3.7929688,7.354 0.4394532,4.0004844Z' fill='%23545560'/%3E%3C/svg%3E"); +} + +.breadcrumb__link:hover, +.breadcrumb__link:focus { + text-decoration: none; +} diff --git a/core/themes/claro/css/src/components/breadcrumb.pcss.css b/core/themes/claro/css/src/components/breadcrumb.pcss.css new file mode 100644 index 0000000000..288d9c9f36 --- /dev/null +++ b/core/themes/claro/css/src/components/breadcrumb.pcss.css @@ -0,0 +1,41 @@ +/** + * @file + * Breadcrumbs. + */ + +@import "../base/variables.pcss.css"; + +.breadcrumb { + padding: 0; + color: var(--color-text); + font-size: 0.79rem; +} + +.breadcrumb__list, +[dir="rtl"] .breadcrumb__list { + margin: 0; + padding: 0; + list-style-type: none; +} + +.breadcrumb__item, +.breadcrumb__link { + display: inline; + text-decoration: none; + color: var(--color-text); + font-weight: bold; +} + +.breadcrumb__item + .breadcrumb__item::before { + padding: 0 0.75rem; + content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 5 8' height='8' width='5'%3E%3Cpath d='M1.2070312,0.64696878 0.5,1.354 3.1464844,4.0004844 0.5,6.6469688 1.2070312,7.354 4.5605468,4.0004844Z' fill='%23545560'/%3E%3C/svg%3E"); +} + +[dir="rtl"] .breadcrumb__item + .breadcrumb__item::before { + content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='5' height='8' viewBox='0 0 5 8'%3E%3Cpath d='M3.7929688,0.64696878 4.5,1.354 1.8535156,4.0004844 4.5,6.6469688 3.7929688,7.354 0.4394532,4.0004844Z' fill='%23545560'/%3E%3C/svg%3E"); +} + +.breadcrumb__link:hover, +.breadcrumb__link:focus { + text-decoration: none; +} diff --git a/core/themes/claro/css/src/components/button.css b/core/themes/claro/css/src/components/button.css new file mode 100644 index 0000000000..57418eb1d5 --- /dev/null +++ b/core/themes/claro/css/src/components/button.css @@ -0,0 +1,247 @@ +/* + * DO NOT EDIT THIS FILE. + * See the following change record for more information, + * https://www.drupal.org/node/2815083 + * @preserve + */ + +/** + * @file + * Structural styles for Claro's UI buttons + * + * Apply these classes to button elements (`; +})(Drupal); diff --git a/core/themes/claro/js/dropbutton.js b/core/themes/claro/js/dropbutton.js new file mode 100644 index 0000000000..e572550bcf --- /dev/null +++ b/core/themes/claro/js/dropbutton.js @@ -0,0 +1,12 @@ +/** +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ + +(function (Drupal) { + Drupal.theme.dropbuttonToggle = function (options) { + return "
  • "; + }; +})(Drupal); \ No newline at end of file diff --git a/core/themes/claro/js/messages.es6.js b/core/themes/claro/js/messages.es6.js new file mode 100644 index 0000000000..50ffa866ef --- /dev/null +++ b/core/themes/claro/js/messages.es6.js @@ -0,0 +1,74 @@ +/** + * @file + * Message template overrides. + */ + +(Drupal => { + /** + * Override Drupal.Message.defaultWrapper() because it prevents adding classes + * to the wrapper. + * + * @return {HTMLElement} + * The default destination for JavaScript messages. + * + * @todo Revisit this after https://www.drupal.org/node/3086723 has been + * resolved. + */ + Drupal.Message.defaultWrapper = () => { + let wrapper = document.querySelector('[data-drupal-messages]'); + if (!wrapper) { + wrapper = document.querySelector('[data-drupal-messages-fallback]'); + wrapper.removeAttribute('data-drupal-messages-fallback'); + wrapper.setAttribute('data-drupal-messages', ''); + wrapper.classList.remove('hidden'); + wrapper.classList.add('messages-list'); + } + return wrapper.innerHTML === '' + ? Drupal.Message.messageInternalWrapper(wrapper) + : wrapper.firstElementChild; + }; + + /** + * Overrides message theme function. + * + * @param {object} message + * The message object. + * @param {string} message.text + * The message text. + * @param {object} options + * The message context. + * @param {string} options.type + * The message type. + * @param {string} options.id + * ID of the message, for reference. + * + * @return {HTMLElement} + * A DOM Node. + */ + Drupal.theme.message = ({ text }, { type, id }) => { + const messagesTypes = Drupal.Message.getMessageTypeLabels(); + const messageWrapper = document.createElement('div'); + + messageWrapper.setAttribute('class', `messages messages--${type}`); + messageWrapper.setAttribute( + 'role', + type === 'error' || type === 'warning' ? 'alert' : 'status', + ); + messageWrapper.setAttribute('aria-labelledby', `${id}-title`); + messageWrapper.setAttribute('data-drupal-message-id', id); + messageWrapper.setAttribute('data-drupal-message-type', type); + + messageWrapper.innerHTML = ` +
    +

    + ${messagesTypes[type]} +

    +
    +
    + ${text} +
    + `; + + return messageWrapper; + }; +})(Drupal); diff --git a/core/themes/claro/js/messages.js b/core/themes/claro/js/messages.js new file mode 100644 index 0000000000..a90c3e62da --- /dev/null +++ b/core/themes/claro/js/messages.js @@ -0,0 +1,39 @@ +/** +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ + +(function (Drupal) { + Drupal.Message.defaultWrapper = function () { + var wrapper = document.querySelector('[data-drupal-messages]'); + if (!wrapper) { + wrapper = document.querySelector('[data-drupal-messages-fallback]'); + wrapper.removeAttribute('data-drupal-messages-fallback'); + wrapper.setAttribute('data-drupal-messages', ''); + wrapper.classList.remove('hidden'); + wrapper.classList.add('messages-list'); + } + return wrapper.innerHTML === '' ? Drupal.Message.messageInternalWrapper(wrapper) : wrapper.firstElementChild; + }; + + Drupal.theme.message = function (_ref, _ref2) { + var text = _ref.text; + var type = _ref2.type, + id = _ref2.id; + + var messagesTypes = Drupal.Message.getMessageTypeLabels(); + var messageWrapper = document.createElement('div'); + + messageWrapper.setAttribute('class', 'messages messages--' + type); + messageWrapper.setAttribute('role', type === 'error' || type === 'warning' ? 'alert' : 'status'); + messageWrapper.setAttribute('aria-labelledby', id + '-title'); + messageWrapper.setAttribute('data-drupal-message-id', id); + messageWrapper.setAttribute('data-drupal-message-type', type); + + messageWrapper.innerHTML = '\n
    \n

    \n ' + messagesTypes[type] + '\n

    \n
    \n
    \n ' + text + '\n
    \n '; + + return messageWrapper; + }; +})(Drupal); \ No newline at end of file diff --git a/core/themes/claro/js/mobile.install.es6.js b/core/themes/claro/js/mobile.install.es6.js new file mode 100644 index 0000000000..a55e120b45 --- /dev/null +++ b/core/themes/claro/js/mobile.install.es6.js @@ -0,0 +1,29 @@ +(() => { + function findActiveStep(steps) { + for (let i = 0; i < steps.length; i++) { + if (steps[i].className === 'is-active') { + return i + 1; + } + } + // The final "Finished" step is never "active". + if (steps[steps.length - 1].className === 'done') { + return steps.length; + } + return 0; + } + + function installStepsSetup() { + const steps = document.querySelectorAll('.task-list li'); + if (steps.length) { + const header = document.querySelector('header[role="banner"]'); + const stepIndicator = document.createElement('div'); + stepIndicator.className = 'step-indicator'; + stepIndicator.innerHTML = `${findActiveStep(steps)}/${steps.length}`; + header.appendChild(stepIndicator); + } + } + + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', installStepsSetup); + } +})(); diff --git a/core/themes/claro/js/mobile.install.js b/core/themes/claro/js/mobile.install.js new file mode 100644 index 0000000000..57c2823fca --- /dev/null +++ b/core/themes/claro/js/mobile.install.js @@ -0,0 +1,36 @@ +/** +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ + +(function () { + function findActiveStep(steps) { + for (var i = 0; i < steps.length; i++) { + if (steps[i].className === 'is-active') { + return i + 1; + } + } + + if (steps[steps.length - 1].className === 'done') { + return steps.length; + } + return 0; + } + + function installStepsSetup() { + var steps = document.querySelectorAll('.task-list li'); + if (steps.length) { + var header = document.querySelector('header[role="banner"]'); + var stepIndicator = document.createElement('div'); + stepIndicator.className = 'step-indicator'; + stepIndicator.innerHTML = findActiveStep(steps) + '/' + steps.length; + header.appendChild(stepIndicator); + } + } + + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', installStepsSetup); + } +})(); \ No newline at end of file diff --git a/core/themes/claro/js/nav-tabs.es6.js b/core/themes/claro/js/nav-tabs.es6.js new file mode 100644 index 0000000000..9971585319 --- /dev/null +++ b/core/themes/claro/js/nav-tabs.es6.js @@ -0,0 +1,88 @@ +/** + * @file + * Responsive navigation tabs. + * + * This also supports collapsible navigable is the 'is-collapsible' class is + * added to the main element, and a target element is included. + */ +(($, Drupal) => { + function init(i, tab) { + const $tab = $(tab); + const $target = $tab.find('[data-drupal-nav-tabs-target]'); + const $active = $target.find('.js-active-tab'); + + const openMenu = () => { + $target.toggleClass('is-open'); + }; + + const toggleOrder = reset => { + const current = $active.index(); + const original = $active.data('original-order'); + + // Do not change order if already first or if already reset. + if (original === 0 || reset === (current === original)) { + return; + } + + const siblings = { + first: '[data-original-order="0"]', + previous: `[data-original-order="${original - 1}"]`, + }; + + const $first = $target.find(siblings.first); + const $previous = $target.find(siblings.previous); + + if (reset && current !== original) { + $active.insertAfter($previous); + } else if (!reset && current === original) { + $active.insertBefore($first); + } + }; + + const toggleCollapsed = () => { + if (window.matchMedia('(min-width: 48em)').matches) { + if ($tab.hasClass('is-horizontal') && !$tab.attr('data-width')) { + let width = 0; + + $target.find('.js-tabs-link').each((index, value) => { + width += $(value).outerWidth(); + }); + $tab.attr('data-width', width); + } + + // Collapse the tabs if the combined width of the tabs is greater than + // the width of the parent container. + const isHorizontal = $tab.attr('data-width') <= $tab.outerWidth(); + $tab.toggleClass('is-horizontal', isHorizontal); + toggleOrder(isHorizontal); + } else { + toggleOrder(false); + } + }; + + $tab.addClass('position-container is-horizontal-enabled'); + + $target.find('.js-tab').each((index, element) => { + const $item = $(element); + $item.attr('data-original-order', $item.index()); + }); + + $tab.on('click.tabs', '[data-drupal-nav-tabs-trigger]', openMenu); + $(window) + .on('resize.tabs', Drupal.debounce(toggleCollapsed, 150)) + .trigger('resize.tabs'); + } + /** + * Initialise the tabs JS. + */ + Drupal.behaviors.navTabs = { + attach(context) { + $(context) + .find('[data-drupal-nav-tabs].is-collapsible') + .once('nav-tabs') + .each((i, value) => { + $(value).each(init); + }); + }, + }; +})(jQuery, Drupal); diff --git a/core/themes/claro/js/nav-tabs.js b/core/themes/claro/js/nav-tabs.js new file mode 100644 index 0000000000..75445a53c7 --- /dev/null +++ b/core/themes/claro/js/nav-tabs.js @@ -0,0 +1,78 @@ +/** +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ + +(function ($, Drupal) { + function init(i, tab) { + var $tab = $(tab); + var $target = $tab.find('[data-drupal-nav-tabs-target]'); + var $active = $target.find('.js-active-tab'); + + var openMenu = function openMenu() { + $target.toggleClass('is-open'); + }; + + var toggleOrder = function toggleOrder(reset) { + var current = $active.index(); + var original = $active.data('original-order'); + + if (original === 0 || reset === (current === original)) { + return; + } + + var siblings = { + first: '[data-original-order="0"]', + previous: '[data-original-order="' + (original - 1) + '"]' + }; + + var $first = $target.find(siblings.first); + var $previous = $target.find(siblings.previous); + + if (reset && current !== original) { + $active.insertAfter($previous); + } else if (!reset && current === original) { + $active.insertBefore($first); + } + }; + + var toggleCollapsed = function toggleCollapsed() { + if (window.matchMedia('(min-width: 48em)').matches) { + if ($tab.hasClass('is-horizontal') && !$tab.attr('data-width')) { + var width = 0; + + $target.find('.js-tabs-link').each(function (index, value) { + width += $(value).outerWidth(); + }); + $tab.attr('data-width', width); + } + + var isHorizontal = $tab.attr('data-width') <= $tab.outerWidth(); + $tab.toggleClass('is-horizontal', isHorizontal); + toggleOrder(isHorizontal); + } else { + toggleOrder(false); + } + }; + + $tab.addClass('position-container is-horizontal-enabled'); + + $target.find('.js-tab').each(function (index, element) { + var $item = $(element); + $item.attr('data-original-order', $item.index()); + }); + + $tab.on('click.tabs', '[data-drupal-nav-tabs-trigger]', openMenu); + $(window).on('resize.tabs', Drupal.debounce(toggleCollapsed, 150)).trigger('resize.tabs'); + } + + Drupal.behaviors.navTabs = { + attach: function attach(context) { + $(context).find('[data-drupal-nav-tabs].is-collapsible').once('nav-tabs').each(function (i, value) { + $(value).each(init); + }); + } + }; +})(jQuery, Drupal); \ No newline at end of file diff --git a/core/themes/claro/js/responsive-details.es6.js b/core/themes/claro/js/responsive-details.es6.js new file mode 100644 index 0000000000..bc31c1c01a --- /dev/null +++ b/core/themes/claro/js/responsive-details.es6.js @@ -0,0 +1,52 @@ +/** + * @file + * Provides responsive behaviors to HTML details elements. + */ + +(($, Drupal) => { + /** + * Initializes the responsive behaviors for details elements. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the responsive behavior to status report specific details elements. + */ + Drupal.behaviors.responsiveDetails = { + attach(context) { + const $details = $(context) + .find('details') + .once('responsive-details'); + + if (!$details.length) { + return; + } + + const $summaries = $details.find('> summary'); + + function detailsToggle(matches) { + if (matches) { + $details.attr('open', true); + $summaries.attr('aria-expanded', true); + $summaries.on('click.details-open', false); + } else { + // If user explicitly opened one, leave it alone. + const $notPressed = $details + .find('> summary[aria-pressed!=true]') + .attr('aria-expanded', false); + $notPressed.parent('details').attr('open', false); + // After resize, allow user to close previously opened details. + $summaries.off('.details-open'); + } + } + + function handleDetailsMQ(event) { + detailsToggle(event.matches); + } + + const mql = window.matchMedia('(min-width:48em)'); + mql.addListener(handleDetailsMQ); + detailsToggle(mql.matches); + }, + }; +})(jQuery, Drupal); diff --git a/core/themes/claro/js/responsive-details.js b/core/themes/claro/js/responsive-details.js new file mode 100644 index 0000000000..3c62e3cef6 --- /dev/null +++ b/core/themes/claro/js/responsive-details.js @@ -0,0 +1,41 @@ +/** +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ + +(function ($, Drupal) { + Drupal.behaviors.responsiveDetails = { + attach: function attach(context) { + var $details = $(context).find('details').once('responsive-details'); + + if (!$details.length) { + return; + } + + var $summaries = $details.find('> summary'); + + function detailsToggle(matches) { + if (matches) { + $details.attr('open', true); + $summaries.attr('aria-expanded', true); + $summaries.on('click.details-open', false); + } else { + var $notPressed = $details.find('> summary[aria-pressed!=true]').attr('aria-expanded', false); + $notPressed.parent('details').attr('open', false); + + $summaries.off('.details-open'); + } + } + + function handleDetailsMQ(event) { + detailsToggle(event.matches); + } + + var mql = window.matchMedia('(min-width:48em)'); + mql.addListener(handleDetailsMQ); + detailsToggle(mql.matches); + } + }; +})(jQuery, Drupal); \ No newline at end of file diff --git a/core/themes/claro/js/tabledrag.es6.js b/core/themes/claro/js/tabledrag.es6.js new file mode 100644 index 0000000000..1e1b591e2b --- /dev/null +++ b/core/themes/claro/js/tabledrag.es6.js @@ -0,0 +1,1883 @@ +/** + * @file + * Overrides tabledrag.js that provides dragging capabilities. + * + * - New Drupal.theme.tableDragHandle() function for tabledrag handle markup + * (https://www.drupal.org/node/3077938). + * - New Drupal.theme.tableDragToggle() function for tabledrag toggle markup + * (@todo: https://www.drupal.org/node/3084916). + * - New Drupal.theme.tableDragToggleWrapper() function for the wrapper of the + * tabledrag toggle (@todo: https://www.drupal.org/node/3084916). + * - Tabledrag functionality can be disabled + * (https://www.drupal.org/node/3083039). + * - The initial content of the tabledrag-cell is wrapped into a new DOM element + * ".tabledrag-cell-content__item". This new element is moved into an another + * ".tabledrag-cell-content" division that contains the drag handle, the + * identation elements and the tabledrag changed mark as well. + * This is needed to keep all of these element in a single line + * (https://www.drupal.org/node/3083044). + * Claro introduced two theme functions for these: + * - Drupal.theme.tableDragCellContentWrapper() provides the output of the + * original content of the first table cell. + * - Drupal.theme.tableDragCellItemsWrapper() provides the markup of the + * common wrapper for every tabledrag cell elements including the + * indentation(s), the drag-handle, the original content and the tabledrag + * changed marker. + * - Fixes the RTL bug of the original tabledrag.js + * (https://www.drupal.org/node/197641). + * - Tabledrag changed mark is added next to the drag-handle, and not after the + * last item. (@todo: https://www.drupal.org/node/3084910). + * + * The '_slicedToArray' shim added for handling destructured arrays breaks IE11, + * that is why the 'prefer-destructuring' rule is disabled. + * @see https://github.com/babel/babel/issues/7597. + * + * @todo Refactor after https://www.drupal.org/node/3077938, + * https://www.drupal.org/node/3083039, https://www.drupal.org/node/3083044 + * and https://www.drupal.org/node/197641 are in. + */ + +/** + * Triggers when weights columns are toggled. + * + * @event columnschange + */ + +/* eslint-disable default-case, new-cap, prefer-destructuring */ +(($, Drupal, drupalSettings) => { + /** + * Store the state of weight columns display for all tables. + * + * Default value is to hide weight columns. + */ + let showWeight = JSON.parse( + localStorage.getItem('Drupal.tableDrag.showWeight'), + ); + + /** + * Drag and drop table rows with field manipulation. + * + * Using the drupal_attach_tabledrag() function, any table with weights or + * parent relationships may be made into draggable tables. Columns containing + * a field may optionally be hidden, providing a better user experience. + * + * Created tableDrag instances may be modified with custom behaviors by + * overriding the .onDrag, .onDrop, .row.onSwap, and .row.onIndent methods. + * See blocks.js for an example of adding additional functionality to + * tableDrag. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.tableDrag = { + attach(context, settings) { + function initTableDrag(table, base) { + if (table.length) { + // Create the new tableDrag instance. Save in the Drupal variable + // to allow other scripts access to the object. + Drupal.tableDrag[base] = new Drupal.tableDrag( + table[0], + settings.tableDrag[base], + ); + } + } + + Object.keys(settings.tableDrag || {}).forEach(base => { + initTableDrag( + $(context) + .find(`#${base}`) + .once('tabledrag'), + base, + ); + }); + }, + }; + + /** + * Provides table and field manipulation. + * + * @constructor + * + * @param {HTMLElement} table + * DOM object for the table to be made draggable. + * @param {object} tableSettings + * Settings for the table added via drupal_add_dragtable(). + */ + Drupal.tableDrag = function init(table, tableSettings) { + const self = this; + const $table = $(table); + + /** + * @type {jQuery} + */ + this.$table = $(table); + + /** + * + * @type {HTMLElement} + */ + this.table = table; + + /** + * @type {object} + */ + this.tableSettings = tableSettings; + + /** + * Used to hold information about a current drag operation. + * + * @type {?HTMLElement} + */ + this.dragObject = null; + + /** + * Provides operations for row manipulation. + * + * @type {?HTMLElement} + */ + this.rowObject = null; + + /** + * Remember the previous element. + * + * @type {?HTMLElement} + */ + this.oldRowElement = null; + + /** + * Used to determine up or down direction from last mouse move. + * + * @type {number} + */ + this.oldY = 0; + + /** + * Whether anything in the entire table has changed. + * + * @type {bool} + */ + this.changed = false; + + /** + * Maximum amount of allowed parenting. + * + * @type {number} + */ + this.maxDepth = 0; + + /** + * Direction of the table. + * + * @type {number} + */ + this.rtl = $(this.table).css('direction') === 'rtl' ? -1 : 1; + + /** + * + * @type {bool} + */ + this.striping = $(this.table).data('striping') === 1; + + /** + * Configure the scroll settings. + * + * @type {object} + * + * @prop {number} amount + * @prop {number} interval + * @prop {number} trigger + */ + this.scrollSettings = { amount: 4, interval: 50, trigger: 70 }; + + /** + * + * @type {?number} + */ + this.scrollInterval = null; + + /** + * + * @type {number} + */ + this.scrollY = 0; + + /** + * + * @type {number} + */ + this.windowHeight = 0; + + /** + * Check this table's settings for parent relationships. + * + * For efficiency, large sections of code can be skipped if we don't need to + * track horizontal movement and indentations. + * + * @type {bool} + */ + this.indentEnabled = false; + Object.keys(tableSettings || {}).forEach(group => { + Object.keys(tableSettings[group] || {}).forEach(n => { + if (tableSettings[group][n].relationship === 'parent') { + this.indentEnabled = true; + } + if (tableSettings[group][n].limit > 0) { + this.maxDepth = tableSettings[group][n].limit; + } + }); + }); + if (this.indentEnabled) { + /** + * Total width of indents, set in makeDraggable. + * + * @type {number} + */ + this.indentCount = 1; + // Find the width of indentations to measure mouse movements against. + // Because the table doesn't need to start with any indentations, we + // manually append 2 indentations in the first draggable row, measure + // the offset, then remove. + const indent = Drupal.theme('tableDragIndentation'); + const testRow = $('') + .addClass('draggable') + .appendTo(table); + const testCell = $('') + .appendTo(testRow) + .prepend(indent) + .prepend(indent); + const $indentation = testCell.find('.js-indentation'); + + /** + * @type {number} + */ + this.indentAmount = + $indentation.get(1).offsetLeft - $indentation.get(0).offsetLeft; + testRow.remove(); + } + + // Make each applicable row draggable. + // Match immediate children of the parent element to allow nesting. + $table + .find('> tr.draggable, > tbody > tr.draggable') + .each(function initDraggable() { + self.makeDraggable(this); + }); + + // Add the toggle link wrapper before the table that will contain the toggle + // for users to show or hide weight columns. + $table.before( + $(Drupal.theme('tableDragToggleWrapper')) + .addClass('js-tabledrag-toggle-weight-wrapper') + .on( + 'click', + '.js-tabledrag-toggle-weight', + $.proxy(function toggleColumns(event) { + event.preventDefault(); + this.toggleColumns(); + }, this), + ), + ); + + // Initialize the specified columns (for example, weight or parent columns) + // to show or hide according to user preference. This aids accessibility + // so that, e.g., screen reader users can choose to enter weight values and + // manipulate form elements directly, rather than using drag-and-drop.. + self.initColumns(); + + // Add event bindings to the document. The self variable is passed along + // as event handlers do not have direct access to the tableDrag object. + $(document).on('touchmove', event => + self.dragRow(event.originalEvent.touches[0], self), + ); + $(document).on('touchend', event => + self.dropRow(event.originalEvent.touches[0], self), + ); + $(document).on('mousemove pointermove', event => self.dragRow(event, self)); + $(document).on('mouseup pointerup', event => self.dropRow(event, self)); + + // React to localStorage event showing or hiding weight columns. + $(window).on( + 'storage', + $.proxy(function weightColumnDisplayChange(event) { + // Only react to 'Drupal.tableDrag.showWeight' value change. + if (event.originalEvent.key === 'Drupal.tableDrag.showWeight') { + // This was changed in another window, get the new value for this + // window. + showWeight = JSON.parse(event.originalEvent.newValue); + this.displayColumns(showWeight); + } + }, this), + ); + }; + + $.extend(Drupal.tableDrag.prototype, { + /** + * Initialize columns containing form elements to be hidden by default. + * + * Identify and mark each cell with a CSS class so we can easily toggle + * show/hide it. Finally, hide columns if user does not have a + * 'Drupal.tableDrag.showWeight' localStorage value. + */ + initColumns() { + const { $table } = this; + let hidden; + let cell; + let columnIndex; + Object.keys(this.tableSettings || {}).forEach(group => { + // Find the first field in this group. + Object.keys(this.tableSettings[group]).some(tableSetting => { + const field = $table + .find(`.${this.tableSettings[group][tableSetting].target}`) + .eq(0); + if (field.length && this.tableSettings[group][tableSetting].hidden) { + hidden = this.tableSettings[group][tableSetting].hidden; + cell = field.closest('td'); + return true; + } + return false; + }); + + // Mark the column containing this field so it can be hidden. + if (hidden && cell[0]) { + // Add 1 to our indexes. The nth-child selector is 1 based, not 0 + // based. Match immediate children of the parent element to allow + // nesting. + columnIndex = + cell + .parent() + .find('> td') + .index(cell.get(0)) + 1; + $table + .find('> thead > tr, > tbody > tr, > tr') + .each(this.addColspanClass(columnIndex)); + } + }); + this.displayColumns(showWeight); + }, + + /** + * Mark cells that have colspan. + * + * In order to adjust the colspan instead of hiding them altogether. + * + * @param {number} columnIndex + * The column index to add colspan class to. + * + * @return {function} + * Function to add colspan class. + */ + addColspanClass(columnIndex) { + return function addColspanClass() { + // Get the columnIndex and adjust for any colspans in this row. + const $row = $(this); + let index = columnIndex; + const cells = $row.children(); + let cell; + cells.each(function checkColspan(n) { + if (n < index && this.colSpan && this.colSpan > 1) { + index -= this.colSpan - 1; + } + }); + if (index > 0) { + cell = cells.filter(`:nth-child(${index})`); + if (cell[0].colSpan && cell[0].colSpan > 1) { + // If this cell has a colspan, mark it so we can reduce the colspan. + cell.addClass('tabledrag-has-colspan'); + } else { + // Mark this cell so we can hide it. + cell.addClass('tabledrag-hide'); + } + } + }; + }, + + /** + * Hide or display weight columns. Triggers an event on change. + * + * @fires event:columnschange + * + * @param {bool} displayWeight + * 'true' will show weight columns. + */ + displayColumns(displayWeight) { + if (displayWeight) { + this.showColumns(); + } + // Default action is to hide columns. + else { + this.hideColumns(); + } + // Trigger an event to allow other scripts to react to this display change. + // Force the extra parameter as a bool. + $('table') + .findOnce('tabledrag') + .trigger('columnschange', !!displayWeight); + }, + + /** + * Toggle the weight column depending on 'showWeight' value. + * + * Store only default override. + */ + toggleColumns() { + showWeight = !showWeight; + this.displayColumns(showWeight); + if (showWeight) { + // Save default override. + localStorage.setItem('Drupal.tableDrag.showWeight', showWeight); + } else { + // Reset the value to its default. + localStorage.removeItem('Drupal.tableDrag.showWeight'); + } + }, + + /** + * Hide the columns containing weight/parent form elements. + * + * Undo showColumns(). + */ + hideColumns() { + const $tables = $('table').findOnce('tabledrag'); + // Hide weight/parent cells and headers. + $tables.find('.tabledrag-hide').css('display', 'none'); + // Show TableDrag handles. + $tables.find('.js-tabledrag-handle').css('display', ''); + // Reduce the colspan of any effected multi-span columns. + $tables.find('.tabledrag-has-colspan').each(function decreaseColspan() { + this.colSpan = this.colSpan - 1; + }); + // Change link text. + $('.js-tabledrag-toggle-weight-wrapper').each( + function addShowWeightToggle() { + const $wrapper = $(this); + const toggleWasFocused = $wrapper.find( + '.js-tabledrag-toggle-weight:focus', + ).length; + $wrapper + .empty() + .append( + $( + Drupal.theme( + 'tableDragToggle', + 'show', + Drupal.t('Show row weights'), + ), + ).addClass('js-tabledrag-toggle-weight'), + ); + if (toggleWasFocused) { + $wrapper.find('.js-tabledrag-toggle-weight').trigger('focus'); + } + }, + ); + }, + + /** + * Show the columns containing weight/parent form elements. + * + * Undo hideColumns(). + */ + showColumns() { + const $tables = $('table').findOnce('tabledrag'); + // Show weight/parent cells and headers. + $tables.find('.tabledrag-hide').css('display', ''); + // Hide TableDrag handles. + $tables.find('.js-tabledrag-handle').css('display', 'none'); + // Increase the colspan for any columns where it was previously reduced. + $tables.find('.tabledrag-has-colspan').each(function increaseColspan() { + this.colSpan = this.colSpan + 1; + }); + // Change link text. + $('.js-tabledrag-toggle-weight-wrapper').each( + function addHideWeightToggle() { + const $wrapper = $(this); + const toggleWasFocused = $wrapper.find( + '.js-tabledrag-toggle-weight:focus', + ).length; + $wrapper + .empty() + .append( + $( + Drupal.theme( + 'tableDragToggle', + 'hide', + Drupal.t('Hide row weights'), + ), + ).addClass('js-tabledrag-toggle-weight'), + ); + if (toggleWasFocused) { + $wrapper.find('.js-tabledrag-toggle-weight').trigger('focus'); + } + }, + ); + }, + + /** + * Find the target used within a particular row and group. + * + * @param {string} group + * Group selector. + * @param {HTMLElement} row + * The row HTML element. + * + * @return {object} + * The table row settings. + */ + rowSettings(group, row) { + const field = $(row).find(`.${group}`); + const tableSettingsGroup = this.tableSettings[group]; + return Object.keys(tableSettingsGroup) + .map(delta => { + const targetClass = tableSettingsGroup[delta].target; + let rowSettings; + if (field.is(`.${targetClass}`)) { + // Return a copy of the row settings. + rowSettings = {}; + Object.keys(tableSettingsGroup[delta]).forEach(n => { + rowSettings[n] = tableSettingsGroup[delta][n]; + }); + } + return rowSettings; + }) + .filter(rowSetting => rowSetting)[0]; + }, + + /** + * Take an item and add event handlers to make it become draggable. + * + * @param {HTMLElement} item + * The item to add event handlers to. + */ + makeDraggable(item) { + const self = this; + const $item = $(item); + const $firstCell = $item + .find('td:first-of-type') + .wrapInner(Drupal.theme.tableDragCellContentWrapper()) + .wrapInner( + $(Drupal.theme('tableDragCellItemsWrapper')).addClass( + 'js-tabledrag-cell-content', + ), + ); + const $targetElem = $firstCell.find('.js-tabledrag-cell-content').length + ? $firstCell.find('.js-tabledrag-cell-content') + : $firstCell.addClass('js-tabledrag-cell-content'); + + // Move indentations into the '.js-tabledrag-cell-content' target. + $targetElem + .find('.js-indentation') + .detach() + .prependTo($targetElem); + + // Add a class to the title link. + $targetElem.find('a').addClass('menu-item__link'); + // Create the handle. + const handle = $(Drupal.theme.tableDragHandle()) + .addClass('js-tabledrag-handle') + .attr('title', Drupal.t('Drag to re-order')); + // Insert the handle after indentations (if any). + const $indentationLast = $targetElem.find('.js-indentation').eq(-1); + if ($indentationLast.length) { + $indentationLast.after(handle); + // Update the total width of indentation in this entire table. + self.indentCount = Math.max( + $item.find('.js-indentation').length, + self.indentCount, + ); + } else { + $targetElem.prepend(handle); + } + + // Prevent the anchor tag from jumping us to the top of the page. + handle.on('click', event => { + event.preventDefault(); + }); + + // Don't do anything if tabledrag is disabled. + if (handle.closest('.js-tabledrag-disabled').length) { + return; + } + + handle.on('mousedown touchstart pointerdown', event => { + event.preventDefault(); + if (event.originalEvent.type === 'touchstart') { + event = event.originalEvent.touches[0]; + } + self.dragStart(event, self, item); + }); + + // Set blur cleanup when a handle is focused. + handle.on('focus', () => { + self.safeBlur = true; + }); + + // On blur, fire the same function as a touchend/mouseup. This is used to + // update values after a row has been moved through the keyboard support. + handle.on('blur', event => { + if (self.rowObject && self.safeBlur) { + self.dropRow(event, self); + } + }); + + // Add arrow-key support to the handle. + handle.on('keydown', event => { + // If a rowObject doesn't yet exist and this isn't the tab key. + if (event.keyCode !== 9 && !self.rowObject) { + self.rowObject = new self.row( + item, + 'keyboard', + self.indentEnabled, + self.maxDepth, + true, + ); + } + + let keyChange = false; + let groupHeight; + + /* eslint-disable no-fallthrough */ + + switch (event.keyCode) { + // Left arrow. + case 37: + // Safari left arrow. + case 63234: + keyChange = true; + self.rowObject.indent(-1 * self.rtl); + break; + + // Up arrow. + case 38: + // Safari up arrow. + case 63232: { + let $previousRow = $(self.rowObject.element) + .prev('tr') + .eq(0); + let previousRow = $previousRow.get(0); + while (previousRow && $previousRow.is(':hidden')) { + $previousRow = $(previousRow) + .prev('tr') + .eq(0); + previousRow = $previousRow.get(0); + } + if (previousRow) { + // Do not allow the onBlur cleanup. + self.safeBlur = false; + self.rowObject.direction = 'up'; + keyChange = true; + + if ($(item).is('.tabledrag-root')) { + // Swap with the previous top-level row. + groupHeight = 0; + while ( + previousRow && + $previousRow.find('.js-indentation').length + ) { + $previousRow = $(previousRow) + .prev('tr') + .eq(0); + previousRow = $previousRow.get(0); + groupHeight += $previousRow.is(':hidden') + ? 0 + : previousRow.offsetHeight; + } + if (previousRow) { + self.rowObject.swap('before', previousRow); + // No need to check for indentation, 0 is the only valid one. + window.scrollBy(0, -groupHeight); + } + } else if ( + self.table.tBodies[0].rows[0] !== previousRow || + $previousRow.is('.draggable') + ) { + // Swap with the previous row (unless previous row is the first + // one and undraggable). + self.rowObject.swap('before', previousRow); + self.rowObject.interval = null; + self.rowObject.indent(0); + window.scrollBy(0, -parseInt(item.offsetHeight, 10)); + } + // Regain focus after the DOM manipulation. + handle.trigger('focus'); + } + break; + } + // Right arrow. + case 39: + // Safari right arrow. + case 63235: + keyChange = true; + self.rowObject.indent(self.rtl); + break; + + // Down arrow. + case 40: + // Safari down arrow. + case 63233: { + let $nextRow = $(self.rowObject.group) + .eq(-1) + .next('tr') + .eq(0); + let nextRow = $nextRow.get(0); + while (nextRow && $nextRow.is(':hidden')) { + $nextRow = $(nextRow) + .next('tr') + .eq(0); + nextRow = $nextRow.get(0); + } + if (nextRow) { + // Do not allow the onBlur cleanup. + self.safeBlur = false; + self.rowObject.direction = 'down'; + keyChange = true; + + if ($(item).is('.tabledrag-root')) { + // Swap with the next group (necessarily a top-level one). + groupHeight = 0; + const nextGroup = new self.row( + nextRow, + 'keyboard', + self.indentEnabled, + self.maxDepth, + false, + ); + if (nextGroup) { + $(nextGroup.group).each(function groupIterator() { + groupHeight += $(this).is(':hidden') + ? 0 + : this.offsetHeight; + }); + const nextGroupRow = $(nextGroup.group) + .eq(-1) + .get(0); + self.rowObject.swap('after', nextGroupRow); + // No need to check for indentation, 0 is the only valid one. + window.scrollBy(0, parseInt(groupHeight, 10)); + } + } else { + // Swap with the next row. + self.rowObject.swap('after', nextRow); + self.rowObject.interval = null; + self.rowObject.indent(0); + window.scrollBy(0, parseInt(item.offsetHeight, 10)); + } + // Regain focus after the DOM manipulation. + handle.trigger('focus'); + } + break; + } + } + + /* eslint-enable no-fallthrough */ + + if (self.rowObject && self.rowObject.changed === true) { + $(item).addClass('drag'); + if (self.oldRowElement) { + $(self.oldRowElement).removeClass('drag-previous'); + } + self.oldRowElement = item; + if (self.striping === true) { + self.restripeTable(); + } + self.onDrag(); + } + + // Returning false if we have an arrow key to prevent scrolling. + if (keyChange) { + return false; + } + }); + + // Compatibility addition, return false on keypress to prevent unwanted + // scrolling. IE and Safari will suppress scrolling on keydown, but all + // other browsers need to return false on keypress. + // http://www.quirksmode.org/js/keys.html + handle.on('keypress', event => { + /* eslint-disable no-fallthrough */ + + switch (event.keyCode) { + // Left arrow. + case 37: + // Up arrow. + case 38: + // Right arrow. + case 39: + // Down arrow. + case 40: + return false; + } + + /* eslint-enable no-fallthrough */ + }); + }, + + /** + * Pointer event initiator, creates drag object and information. + * + * @param {jQuery.Event} event + * The event object that trigger the drag. + * @param {Drupal.tableDrag} self + * The drag handle. + * @param {HTMLElement} item + * The item that that is being dragged. + */ + dragStart(event, self, item) { + // Create a new dragObject recording the pointer information. + self.dragObject = {}; + self.dragObject.initOffset = self.getPointerOffset(item, event); + self.dragObject.initPointerCoords = self.pointerCoords(event); + if (self.indentEnabled) { + self.dragObject.indentPointerPos = self.dragObject.initPointerCoords; + } + + // If there's a lingering row object from the keyboard, remove its focus. + if (self.rowObject) { + $(self.rowObject.element) + .find('.js-tabledrag-handle') + .trigger('blur'); + } + + // Create a new rowObject for manipulation of this row. + self.rowObject = new self.row( + item, + 'pointer', + self.indentEnabled, + self.maxDepth, + true, + ); + + // Save the position of the table. + self.table.topY = $(self.table).offset().top; + self.table.bottomY = self.table.topY + self.table.offsetHeight; + + // Add classes to the handle and row. + $(item).addClass('drag'); + + // Set the document to use the move cursor during drag. + $('body').addClass('drag'); + if (self.oldRowElement) { + $(self.oldRowElement).removeClass('drag-previous'); + } + }, + + /** + * Pointer movement handler, bound to document. + * + * @param {jQuery.Event} event + * The pointer event. + * @param {Drupal.tableDrag} self + * The tableDrag instance. + * + * @return {bool|undefined} + * Undefined if no dragObject is defined, false otherwise. + */ + dragRow(event, self) { + if (self.dragObject) { + self.currentPointerCoords = self.pointerCoords(event); + const y = self.currentPointerCoords.y - self.dragObject.initOffset.y; + const x = self.currentPointerCoords.x - self.dragObject.initOffset.x; + + // Check for row swapping and vertical scrolling. + if (y !== self.oldY) { + self.rowObject.direction = y > self.oldY ? 'down' : 'up'; + // Update the old value. + self.oldY = y; + // Check if the window should be scrolled (and how fast). + const scrollAmount = self.checkScroll(self.currentPointerCoords.y); + // Stop any current scrolling. + clearInterval(self.scrollInterval); + // Continue scrolling if the mouse has moved in the scroll direction. + if ( + (scrollAmount > 0 && self.rowObject.direction === 'down') || + (scrollAmount < 0 && self.rowObject.direction === 'up') + ) { + self.setScroll(scrollAmount); + } + + // If we have a valid target, perform the swap and restripe the table. + const currentRow = self.findDropTargetRow(x, y); + if (currentRow) { + if (self.rowObject.direction === 'down') { + self.rowObject.swap('after', currentRow, self); + } else { + self.rowObject.swap('before', currentRow, self); + } + if (self.striping === true) { + self.restripeTable(); + } + } + } + + // Similar to row swapping, handle indentations. + if (self.indentEnabled) { + const xDiff = + self.currentPointerCoords.x - self.dragObject.indentPointerPos.x; + // Set the number of indentations the pointer has been moved left or + // right. + const indentDiff = Math.round(xDiff / self.indentAmount); + // Indent the row with our estimated diff, which may be further + // restricted according to the rows around this row. + const indentChange = self.rowObject.indent(indentDiff); + // Update table and pointer indentations. + self.dragObject.indentPointerPos.x += + self.indentAmount * indentChange; + self.indentCount = Math.max(self.indentCount, self.rowObject.indents); + } + + return false; + } + }, + + /** + * Pointerup behavior. + * + * @param {jQuery.Event} event + * The pointer event. + * @param {Drupal.tableDrag} self + * The tableDrag instance. + */ + dropRow(event, self) { + let droppedRow; + let $droppedRow; + + // Drop row functionality. + if (self.rowObject !== null) { + droppedRow = self.rowObject.element; + $droppedRow = $(droppedRow); + // The row is already in the right place so we just release it. + if (self.rowObject.changed === true) { + // Update the fields in the dropped row. + self.updateFields(droppedRow); + + // If a setting exists for affecting the entire group, update all the + // fields in the entire dragged group. + Object.keys(self.tableSettings || {}).forEach(group => { + const rowSettings = self.rowSettings(group, droppedRow); + if (rowSettings.relationship === 'group') { + Object.keys(self.rowObject.children || {}).forEach(n => { + self.updateField(self.rowObject.children[n], group); + }); + } + }); + + self.rowObject.markChanged(); + if (self.changed === false) { + const $messageTarget = $(self.table).prevAll( + '.js-tabledrag-toggle-weight-wrapper', + ).length + ? $(self.table) + .prevAll('.js-tabledrag-toggle-weight-wrapper') + .last() + : self.table; + $(Drupal.theme('tableDragChangedWarning')) + .insertBefore($messageTarget) + .hide() + .fadeIn('slow'); + self.changed = true; + } + } + + if (self.indentEnabled) { + self.rowObject.removeIndentClasses(); + } + if (self.oldRowElement) { + $(self.oldRowElement).removeClass('drag-previous'); + } + $droppedRow.removeClass('drag').addClass('drag-previous'); + self.oldRowElement = droppedRow; + self.onDrop(); + self.rowObject = null; + } + + // Functionality specific only to pointerup events. + if (self.dragObject !== null) { + self.dragObject = null; + $('body').removeClass('drag'); + clearInterval(self.scrollInterval); + } + }, + + /** + * Get the coordinates from the event (allowing for browser differences). + * + * @param {jQuery.Event} event + * The pointer event. + * + * @return {object} + * An object with `x` and `y` keys indicating the position. + */ + pointerCoords(event) { + if (event.pageX || event.pageY) { + return { x: event.pageX, y: event.pageY }; + } + return { + x: + event.clientX + (document.body.scrollLeft - document.body.clientLeft), + y: event.clientY + (document.body.scrollTop - document.body.clientTop), + }; + }, + + /** + * Get the event offset from the target element. + * + * Given a target element and a pointer event, get the event offset from that + * element. To do this we need the element's position and the target position. + * + * @param {HTMLElement} target + * The target HTML element. + * @param {jQuery.Event} event + * The pointer event. + * + * @return {object} + * An object with `x` and `y` keys indicating the position. + */ + getPointerOffset(target, event) { + const docPos = $(target).offset(); + const pointerPos = this.pointerCoords(event); + return { x: pointerPos.x - docPos.left, y: pointerPos.y - docPos.top }; + }, + + /** + * Find the row the mouse is currently over. + * + * This row is then taken and swapped with the one being dragged. + * + * @param {number} x + * The x coordinate of the mouse on the page (not the screen). + * @param {number} y + * The y coordinate of the mouse on the page (not the screen). + * + * @return {*} + * The drop target row, if found. + */ + findDropTargetRow(x, y) { + const rows = $(this.table.tBodies[0].rows).not(':hidden'); + for (let n = 0; n < rows.length; n++) { + let row = rows[n]; + let $row = $(row); + const rowY = $row.offset().top; + let rowHeight; + // Because Safari does not report offsetHeight on table rows, but does on + // table cells, grab the firstChild of the row and use that instead. + // http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari. + if (row.offsetHeight === 0) { + rowHeight = parseInt(row.firstChild.offsetHeight, 10) / 2; + } + // Other browsers. + else { + rowHeight = parseInt(row.offsetHeight, 10) / 2; + } + + // Because we always insert before, we need to offset the height a bit. + if (y > rowY - rowHeight && y < rowY + rowHeight) { + if (this.indentEnabled) { + // Check that this row is not a child of the row being dragged. + if ( + Object.keys(this.rowObject.group).some( + o => this.rowObject.group[o] === row, + ) + ) { + return null; + } + } + // Do not allow a row to be swapped with itself. + else if (row === this.rowObject.element) { + return null; + } + + // Check that swapping with this row is allowed. + if (!this.rowObject.isValidSwap(row)) { + return null; + } + + // We may have found the row the mouse just passed over, but it doesn't + // take into account hidden rows. Skip backwards until we find a + // draggable row. + while ($row.is(':hidden') && $row.prev('tr').is(':hidden')) { + $row = $row.prev('tr:first-of-type'); + row = $row.get(0); + } + return row; + } + } + return null; + }, + + /** + * After the row is dropped, update the table fields. + * + * @param {HTMLElement} changedRow + * DOM object for the row that was just dropped. + */ + updateFields(changedRow) { + Object.keys(this.tableSettings || {}).forEach(group => { + // Each group may have a different setting for relationship, so we find + // the source rows for each separately. + this.updateField(changedRow, group); + }); + }, + + /** + * After the row is dropped, update a single table field. + * + * @param {HTMLElement} changedRow + * DOM object for the row that was just dropped. + * @param {string} group + * The settings group on which field updates will occur. + */ + updateField(changedRow, group) { + let rowSettings = this.rowSettings(group, changedRow); + const $changedRow = $(changedRow); + let sourceRow; + let $previousRow; + let previousRow; + let useSibling; + // Set the row as its own target. + if ( + rowSettings.relationship === 'self' || + rowSettings.relationship === 'group' + ) { + sourceRow = changedRow; + } + // Siblings are easy, check previous and next rows. + else if (rowSettings.relationship === 'sibling') { + $previousRow = $changedRow.prev('tr:first-of-type'); + previousRow = $previousRow.get(0); + const $nextRow = $changedRow.next('tr:first-of-type'); + const nextRow = $nextRow.get(0); + sourceRow = changedRow; + if ( + $previousRow.is('.draggable') && + $previousRow.find(`.${group}`).length + ) { + if (this.indentEnabled) { + if ( + $previousRow.find('.js-indentations').length === + $changedRow.find('.js-indentations').length + ) { + sourceRow = previousRow; + } + } else { + sourceRow = previousRow; + } + } else if ( + $nextRow.is('.draggable') && + $nextRow.find(`.${group}`).length + ) { + if (this.indentEnabled) { + if ( + $nextRow.find('.js-indentations').length === + $changedRow.find('.js-indentations').length + ) { + sourceRow = nextRow; + } + } else { + sourceRow = nextRow; + } + } + } + // Parents, look up the tree until we find a field not in this group. + // Go up as many parents as indentations in the changed row. + else if (rowSettings.relationship === 'parent') { + $previousRow = $changedRow.prev('tr'); + previousRow = $previousRow; + while ( + $previousRow.length && + $previousRow.find('.js-indentation').length >= this.rowObject.indents + ) { + $previousRow = $previousRow.prev('tr'); + previousRow = $previousRow; + } + // If we found a row. + if ($previousRow.length) { + sourceRow = $previousRow.get(0); + } + // Otherwise we went all the way to the left of the table without finding + // a parent, meaning this item has been placed at the root level. + else { + // Use the first row in the table as source, because it's guaranteed to + // be at the root level. Find the first item, then compare this row + // against it as a sibling. + sourceRow = $(this.table) + .find('tr.draggable:first-of-type') + .get(0); + if (sourceRow === this.rowObject.element) { + sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]) + .next('tr.draggable') + .get(0); + } + useSibling = true; + } + } + + // Because we may have moved the row from one category to another, + // take a look at our sibling and borrow its sources and targets. + this.copyDragClasses(sourceRow, changedRow, group); + rowSettings = this.rowSettings(group, changedRow); + + // In the case that we're looking for a parent, but the row is at the top + // of the tree, copy our sibling's values. + if (useSibling) { + rowSettings.relationship = 'sibling'; + rowSettings.source = rowSettings.target; + } + + const targetClass = `.${rowSettings.target}`; + const targetElement = $changedRow.find(targetClass).get(0); + + // Check if a target element exists in this row. + if (targetElement) { + const sourceClass = `.${rowSettings.source}`; + const sourceElement = $(sourceClass, sourceRow).get(0); + switch (rowSettings.action) { + case 'depth': + // Get the depth of the target row. + targetElement.value = $(sourceElement) + .closest('tr') + .find('.js-indentation').length; + break; + + case 'match': + // Update the value. + targetElement.value = sourceElement.value; + break; + + case 'order': { + const siblings = this.rowObject.findSiblings(rowSettings); + if ($(targetElement).is('select')) { + // Get a list of acceptable values. + const values = []; + $(targetElement) + .find('option') + .each(function collectValues() { + values.push(this.value); + }); + const maxVal = values[values.length - 1]; + // Populate the values in the siblings. + $(siblings) + .find(targetClass) + .each(function assignValues() { + // If there are more items than possible values, assign the + // maximum value to the row. + if (values.length > 0) { + this.value = values.shift(); + } else { + this.value = maxVal; + } + }); + } else { + // Assume a numeric input field. + let weight = + parseInt( + $(siblings[0]) + .find(targetClass) + .val(), + 10, + ) || 0; + $(siblings) + .find(targetClass) + .each(function assignWeight() { + this.value = weight; + weight += 1; + }); + } + break; + } + } + } + }, + + /** + * Copy all tableDrag related classes from one row to another. + * + * Copy all special tableDrag classes from one row's form elements to a + * different one, removing any special classes that the destination row + * may have had. + * + * @param {HTMLElement} sourceRow + * The element for the source row. + * @param {HTMLElement} targetRow + * The element for the target row. + * @param {string} group + * The group selector. + */ + copyDragClasses(sourceRow, targetRow, group) { + const sourceElement = $(sourceRow).find(`.${group}`); + const targetElement = $(targetRow).find(`.${group}`); + if (sourceElement.length && targetElement.length) { + targetElement[0].className = sourceElement[0].className; + } + }, + + /** + * Check the suggested scroll of the table. + * + * @param {number} cursorY + * The Y position of the cursor. + * + * @return {number} + * The suggested scroll. + */ + checkScroll(cursorY) { + const de = document.documentElement; + const b = document.body; + const windowHeight = + window.innerHeight || + (de.clientHeight && de.clientWidth !== 0 + ? de.clientHeight + : b.offsetHeight); + this.windowHeight = windowHeight; + let scrollY; + if (document.all) { + scrollY = !de.scrollTop ? b.scrollTop : de.scrollTop; + } else { + scrollY = window.pageYOffset ? window.pageYOffset : window.scrollY; + } + this.scrollY = scrollY; + const { trigger } = this.scrollSettings; + let delta = 0; + + // Return a scroll speed relative to the edge of the screen. + if (cursorY - scrollY > windowHeight - trigger) { + delta = trigger / (windowHeight + (scrollY - cursorY)); + delta = delta > 0 && delta < trigger ? delta : trigger; + return delta * this.scrollSettings.amount; + } + if (cursorY - scrollY < trigger) { + delta = trigger / (cursorY - scrollY); + delta = delta > 0 && delta < trigger ? delta : trigger; + return -delta * this.scrollSettings.amount; + } + }, + + /** + * Set the scroll for the table. + * + * @param {number} scrollAmount + * The amount of scroll to apply to the window. + */ + setScroll(scrollAmount) { + const self = this; + + this.scrollInterval = setInterval(() => { + // Update the scroll values stored in the object. + self.checkScroll(self.currentPointerCoords.y); + const aboveTable = self.scrollY > self.table.topY; + const belowTable = + self.scrollY + self.windowHeight < self.table.bottomY; + if ( + (scrollAmount > 0 && belowTable) || + (scrollAmount < 0 && aboveTable) + ) { + window.scrollBy(0, scrollAmount); + } + }, this.scrollSettings.interval); + }, + + /** + * Command to restripe table properly. + */ + restripeTable() { + // :even and :odd are reversed because jQuery counts from 0 and + // we count from 1, so we're out of sync. + // Match immediate children of the parent element to allow nesting. + $(this.table) + .find('> tbody > tr.draggable, > tr.draggable') + .filter(':visible') + .filter(':odd') + .removeClass('odd') + .addClass('even') + .end() + .filter(':even') + .removeClass('even') + .addClass('odd'); + }, + + /** + * Stub function. Allows a custom handler when a row begins dragging. + * + * @return {null} + * Returns null when the stub function is used. + */ + onDrag() { + return null; + }, + + /** + * Stub function. Allows a custom handler when a row is dropped. + * + * @return {null} + * Returns null when the stub function is used. + */ + onDrop() { + return null; + }, + + /** + * Constructor to make a new object to manipulate a table row. + * + * @param {HTMLElement} tableRow + * The DOM element for the table row we will be manipulating. + * @param {string} method + * The method in which this row is being moved. Either 'keyboard' or + * 'mouse'. + * @param {bool} indentEnabled + * Whether the containing table uses indentations. Used for optimizations. + * @param {number} maxDepth + * The maximum amount of indentations this row may contain. + * @param {bool} addClasses + * Whether we want to add classes to this row to indicate child + * relationships. + */ + row(tableRow, method, indentEnabled, maxDepth, addClasses) { + const $tableRow = $(tableRow); + + this.element = tableRow; + this.method = method; + this.group = [tableRow]; + this.groupDepth = $tableRow.find('.js-indentation').length; + this.changed = false; + this.table = $tableRow.closest('table')[0]; + this.indentEnabled = indentEnabled; + this.maxDepth = maxDepth; + // Direction the row is being moved. + this.direction = ''; + if (this.indentEnabled) { + this.indents = $tableRow.find('.js-indentation').length; + this.children = this.findChildren(addClasses); + this.group = $.merge(this.group, this.children); + // Find the depth of this entire group. + for (let n = 0; n < this.group.length; n++) { + this.groupDepth = Math.max( + $(this.group[n]).find('.js-indentation').length, + this.groupDepth, + ); + } + } + }, + }); + + $.extend(Drupal.tableDrag.prototype.row.prototype, { + /** + * Find all children of rowObject by indentation. + * + * @param {bool} addClasses + * Whether we want to add classes to this row to indicate child + * relationships. + * + * @return {Array} + * An array of children of the row. + */ + findChildren(addClasses) { + const parentIndentation = this.indents; + let currentRow = $(this.element, this.table).next('tr.draggable'); + const rows = []; + let child = 0; + + function rowIndentation(indentNum, el) { + const self = $(el); + if (child === 1 && indentNum === parentIndentation) { + self.addClass('tree-child-first'); + } + if (indentNum === parentIndentation) { + self.addClass('tree-child'); + } else if (indentNum > parentIndentation) { + self.addClass('tree-child-horizontal'); + } + } + + while (currentRow.length) { + // A greater indentation indicates this is a child. + if (currentRow.find('.js-indentation').length > parentIndentation) { + child += 1; + rows.push(currentRow[0]); + if (addClasses) { + currentRow.find('.js-indentation').each(rowIndentation); + } + } else { + break; + } + currentRow = currentRow.next('tr.draggable'); + } + if (addClasses && rows.length) { + $(rows[rows.length - 1]) + .find(`.js-indentation:nth-child(${parentIndentation + 1})`) + .addClass('tree-child-last'); + } + return rows; + }, + + /** + * Ensure that two rows are allowed to be swapped. + * + * @param {HTMLElement} row + * DOM object for the row being considered for swapping. + * + * @return {bool} + * Whether the swap is a valid swap or not. + */ + isValidSwap(row) { + const $row = $(row); + if (this.indentEnabled) { + let prevRow; + let nextRow; + if (this.direction === 'down') { + prevRow = row; + nextRow = $row.next('tr').get(0); + } else { + prevRow = $row.prev('tr').get(0); + nextRow = row; + } + this.interval = this.validIndentInterval(prevRow, nextRow); + + // We have an invalid swap if the valid indentations interval is empty. + if (this.interval.min > this.interval.max) { + return false; + } + } + + // Do not let an un-draggable first row have anything put before it. + if ( + this.table.tBodies[0].rows[0] === row && + $row.is(':not(.draggable)') + ) { + return false; + } + + return true; + }, + + /** + * Perform the swap between two rows. + * + * @param {string} position + * Whether the swap will occur 'before' or 'after' the given row. + * @param {HTMLElement} row + * DOM element what will be swapped with the row group. + */ + swap(position, row) { + // Makes sure only DOM object are passed to Drupal.detachBehaviors(). + this.group.forEach(detachedRow => { + Drupal.detachBehaviors(detachedRow, drupalSettings, 'move'); + }); + $(row)[position](this.group); + // Makes sure only DOM object are passed to Drupal.attachBehaviors()s. + this.group.forEach(attachedRow => { + Drupal.attachBehaviors(attachedRow, drupalSettings); + }); + this.changed = true; + this.onSwap(row); + }, + + /** + * Determine the valid indentations interval for the row at a given position. + * + * @param {?HTMLElement} prevRow + * DOM object for the row before the tested position + * (or null for first position in the table). + * @param {?HTMLElement} nextRow + * DOM object for the row after the tested position + * (or null for last position in the table). + * + * @return {object} + * An object with the keys `min` and `max` to indicate the valid indent + * interval. + */ + validIndentInterval(prevRow, nextRow) { + const $prevRow = $(prevRow); + let maxIndent; + + // Minimum indentation: + // Do not orphan the next row. + const minIndent = nextRow ? $(nextRow).find('.js-indentation').length : 0; + + // Maximum indentation: + if ( + !prevRow || + $prevRow.is(':not(.draggable)') || + $(this.element).is('.tabledrag-root') + ) { + // Do not indent: + // - the first row in the table, + // - rows dragged below a non-draggable row, + // - 'root' rows. + maxIndent = 0; + } else { + // Do not go deeper than as a child of the previous row. + maxIndent = + $prevRow.find('.js-indentation').length + + ($prevRow.is('.tabledrag-leaf') ? 0 : 1); + // Limit by the maximum allowed depth for the table. + if (this.maxDepth) { + maxIndent = Math.min( + maxIndent, + this.maxDepth - (this.groupDepth - this.indents), + ); + } + } + + return { min: minIndent, max: maxIndent }; + }, + + /** + * Indent a row within the legal bounds of the table. + * + * @param {number} indentDiff + * The number of additional indentations proposed for the row (can be + * positive or negative). This number will be adjusted to nearest valid + * indentation level for the row. + * + * @return {number} + * The number of indentations applied. + */ + indent(indentDiff) { + const $group = $(this.group); + // Determine the valid indentations interval if not available yet. + if (!this.interval) { + const prevRow = $(this.element) + .prev('tr') + .get(0); + const nextRow = $group + .eq(-1) + .next('tr') + .get(0); + this.interval = this.validIndentInterval(prevRow, nextRow); + } + + // Adjust to the nearest valid indentation. + let indent = this.indents + indentDiff; + indent = Math.max(indent, this.interval.min); + indent = Math.min(indent, this.interval.max); + indentDiff = indent - this.indents; + + for (let n = 1; n <= Math.abs(indentDiff); n++) { + // Add or remove indentations. + if (indentDiff < 0) { + $group.find('.js-indentation:first-of-type').remove(); + this.indents -= 1; + } else { + $group + .find('.js-tabledrag-cell-content') + .prepend(Drupal.theme('tableDragIndentation')); + this.indents += 1; + } + } + if (indentDiff) { + // Update indentation for this row. + this.changed = true; + this.groupDepth += indentDiff; + this.onIndent(); + } + + return indentDiff; + }, + + /** + * Find all siblings for a row. + * + * According to its subgroup or indentation. Note that the passed-in row is + * included in the list of siblings. + * + * @param {object} rowSettings + * The field settings we're using to identify what constitutes a sibling. + * + * @return {Array} + * An array of siblings. + */ + findSiblings(rowSettings) { + const siblings = []; + const directions = ['prev', 'next']; + const rowIndentation = this.indents; + let checkRowIndentation; + for (let d = 0; d < directions.length; d++) { + let checkRow = $(this.element)[directions[d]](); + while (checkRow.length) { + // Check that the sibling contains a similar target field. + if (checkRow.find(`.${rowSettings.target}`)) { + // Either add immediately if this is a flat table, or check to + // ensure that this row has the same level of indentation. + if (this.indentEnabled) { + checkRowIndentation = checkRow.find('.js-indentation').length; + } + + if (!this.indentEnabled || checkRowIndentation === rowIndentation) { + siblings.push(checkRow[0]); + } else if (checkRowIndentation < rowIndentation) { + // No need to keep looking for siblings when we get to a parent. + break; + } + } else { + break; + } + checkRow = checkRow[directions[d]](); + } + // Since siblings are added in reverse order for previous, reverse the + // completed list of previous siblings. Add the current row and + // continue. + if (directions[d] === 'prev') { + siblings.reverse(); + siblings.push(this.element); + } + } + return siblings; + }, + + /** + * Remove indentation helper classes from the current row group. + */ + removeIndentClasses() { + Object.keys(this.children || {}).forEach(n => { + $(this.children[n]) + .find('.js-indentation') + .removeClass('tree-child') + .removeClass('tree-child-first') + .removeClass('tree-child-last') + .removeClass('tree-child-horizontal'); + }); + }, + + /** + * Add an asterisk or other marker to the changed row. + */ + markChanged() { + const marker = $(Drupal.theme('tableDragChangedMarker')).addClass( + 'js-tabledrag-changed-marker', + ); + const cell = $(this.element).find('td:first-of-type'); + if (cell.find('.js-tabledrag-changed-marker').length === 0) { + cell.find('.js-tabledrag-handle').after(marker); + } + }, + + /** + * Stub function. Allows a custom handler when a row is indented. + * + * @return {null} + * Returns null when the stub function is used. + */ + onIndent() { + return null; + }, + + /** + * Stub function. Allows a custom handler when a row is swapped. + * + * @param {HTMLElement} swappedRow + * The element for the swapped row. + * + * @return {null} + * Returns null when the stub function is used. + */ + // eslint-disable-next-line no-unused-vars + onSwap(swappedRow) { + return null; + }, + }); + + $.extend( + Drupal.theme, + /** @lends Drupal.theme */ { + /** + * @return {string} + * Markup for the marker. + */ + tableDragChangedMarker() { + return `*`; + }, + + /** + * @return {string} + * Markup for the indentation. + */ + tableDragIndentation() { + return '
    '; + }, + + /** + * @return {string} + * Markup for the warning. + */ + tableDragChangedWarning() { + return ``; + }, + + /** + * Constucts the table drag handle. + * + * @return {string} + * A string representing a DOM fragment. + */ + tableDragHandle() { + return '
    '; + }, + + /** + * Constructs the wrapper for the whole table drag cell. + * + * @return {string} + * A string representing a DOM fragment. + */ + tableDragCellItemsWrapper() { + return '
    '; + }, + + /** + * Constructs the wrapper for the initial content of the drag cell. + * + * @return {string} + * A string representing a DOM fragment. + */ + tableDragCellContentWrapper() { + return '
    '; + }, + + /** + * Constructs the weight column toggle. + * + * The 'tabledrag-toggle-weight' CSS class should be kept since it is used + * elsewhere as well (e.g. in tests). + * + * @param {string} action + * The action the toggle will perform. + * @param {string} text + * The text content of the toggle. + * + * @return {string} + * A string representing a DOM fragment. + */ + tableDragToggle(action, text) { + const classes = [ + 'action-link', + 'action-link--extrasmall', + 'tabledrag-toggle-weight', + ]; + switch (action) { + case 'show': + classes.push('action-link--icon-show'); + break; + + default: + classes.push('action-link--icon-hide'); + break; + } + + return `${text}`; + }, + + /** + * Constructs the wrapper of the weight column toggle. + * + * The 'tabledrag-toggle-weight-wrapper' CSS class should be kept since it is used + * by Views UI and inside off-canvas dialogs. + * + * @return {string} + * A string representing a DOM fragment. + */ + tableDragToggleWrapper() { + return '
    '; + }, + }, + ); +})(jQuery, Drupal, drupalSettings); diff --git a/core/themes/claro/js/tabledrag.js b/core/themes/claro/js/tabledrag.js new file mode 100644 index 0000000000..ef37e236c0 --- /dev/null +++ b/core/themes/claro/js/tabledrag.js @@ -0,0 +1,1001 @@ +/** +* DO NOT EDIT THIS FILE. +* See the following change record for more information, +* https://www.drupal.org/node/2815083 +* @preserve +**/ +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + +(function ($, Drupal, drupalSettings) { + var showWeight = JSON.parse(localStorage.getItem('Drupal.tableDrag.showWeight')); + + Drupal.behaviors.tableDrag = { + attach: function attach(context, settings) { + function initTableDrag(table, base) { + if (table.length) { + Drupal.tableDrag[base] = new Drupal.tableDrag(table[0], settings.tableDrag[base]); + } + } + + Object.keys(settings.tableDrag || {}).forEach(function (base) { + initTableDrag($(context).find('#' + base).once('tabledrag'), base); + }); + } + }; + + Drupal.tableDrag = function init(table, tableSettings) { + var _this = this; + + var self = this; + var $table = $(table); + + this.$table = $(table); + + this.table = table; + + this.tableSettings = tableSettings; + + this.dragObject = null; + + this.rowObject = null; + + this.oldRowElement = null; + + this.oldY = 0; + + this.changed = false; + + this.maxDepth = 0; + + this.rtl = $(this.table).css('direction') === 'rtl' ? -1 : 1; + + this.striping = $(this.table).data('striping') === 1; + + this.scrollSettings = { amount: 4, interval: 50, trigger: 70 }; + + this.scrollInterval = null; + + this.scrollY = 0; + + this.windowHeight = 0; + + this.indentEnabled = false; + Object.keys(tableSettings || {}).forEach(function (group) { + Object.keys(tableSettings[group] || {}).forEach(function (n) { + if (tableSettings[group][n].relationship === 'parent') { + _this.indentEnabled = true; + } + if (tableSettings[group][n].limit > 0) { + _this.maxDepth = tableSettings[group][n].limit; + } + }); + }); + if (this.indentEnabled) { + this.indentCount = 1; + + var indent = Drupal.theme('tableDragIndentation'); + var testRow = $('').addClass('draggable').appendTo(table); + var testCell = $('').appendTo(testRow).prepend(indent).prepend(indent); + var $indentation = testCell.find('.js-indentation'); + + this.indentAmount = $indentation.get(1).offsetLeft - $indentation.get(0).offsetLeft; + testRow.remove(); + } + + $table.find('> tr.draggable, > tbody > tr.draggable').each(function initDraggable() { + self.makeDraggable(this); + }); + + $table.before($(Drupal.theme('tableDragToggleWrapper')).addClass('js-tabledrag-toggle-weight-wrapper').on('click', '.js-tabledrag-toggle-weight', $.proxy(function toggleColumns(event) { + event.preventDefault(); + this.toggleColumns(); + }, this))); + + self.initColumns(); + + $(document).on('touchmove', function (event) { + return self.dragRow(event.originalEvent.touches[0], self); + }); + $(document).on('touchend', function (event) { + return self.dropRow(event.originalEvent.touches[0], self); + }); + $(document).on('mousemove pointermove', function (event) { + return self.dragRow(event, self); + }); + $(document).on('mouseup pointerup', function (event) { + return self.dropRow(event, self); + }); + + $(window).on('storage', $.proxy(function weightColumnDisplayChange(event) { + if (event.originalEvent.key === 'Drupal.tableDrag.showWeight') { + showWeight = JSON.parse(event.originalEvent.newValue); + this.displayColumns(showWeight); + } + }, this)); + }; + + $.extend(Drupal.tableDrag.prototype, { + initColumns: function initColumns() { + var _this2 = this; + + var $table = this.$table; + + var hidden = void 0; + var cell = void 0; + var columnIndex = void 0; + Object.keys(this.tableSettings || {}).forEach(function (group) { + Object.keys(_this2.tableSettings[group]).some(function (tableSetting) { + var field = $table.find('.' + _this2.tableSettings[group][tableSetting].target).eq(0); + if (field.length && _this2.tableSettings[group][tableSetting].hidden) { + hidden = _this2.tableSettings[group][tableSetting].hidden; + cell = field.closest('td'); + return true; + } + return false; + }); + + if (hidden && cell[0]) { + columnIndex = cell.parent().find('> td').index(cell.get(0)) + 1; + $table.find('> thead > tr, > tbody > tr, > tr').each(_this2.addColspanClass(columnIndex)); + } + }); + this.displayColumns(showWeight); + }, + addColspanClass: function addColspanClass(columnIndex) { + return function addColspanClass() { + var $row = $(this); + var index = columnIndex; + var cells = $row.children(); + var cell = void 0; + cells.each(function checkColspan(n) { + if (n < index && this.colSpan && this.colSpan > 1) { + index -= this.colSpan - 1; + } + }); + if (index > 0) { + cell = cells.filter(':nth-child(' + index + ')'); + if (cell[0].colSpan && cell[0].colSpan > 1) { + cell.addClass('tabledrag-has-colspan'); + } else { + cell.addClass('tabledrag-hide'); + } + } + }; + }, + displayColumns: function displayColumns(displayWeight) { + if (displayWeight) { + this.showColumns(); + } else { + this.hideColumns(); + } + + $('table').findOnce('tabledrag').trigger('columnschange', !!displayWeight); + }, + toggleColumns: function toggleColumns() { + showWeight = !showWeight; + this.displayColumns(showWeight); + if (showWeight) { + localStorage.setItem('Drupal.tableDrag.showWeight', showWeight); + } else { + localStorage.removeItem('Drupal.tableDrag.showWeight'); + } + }, + hideColumns: function hideColumns() { + var $tables = $('table').findOnce('tabledrag'); + + $tables.find('.tabledrag-hide').css('display', 'none'); + + $tables.find('.js-tabledrag-handle').css('display', ''); + + $tables.find('.tabledrag-has-colspan').each(function decreaseColspan() { + this.colSpan = this.colSpan - 1; + }); + + $('.js-tabledrag-toggle-weight-wrapper').each(function addShowWeightToggle() { + var $wrapper = $(this); + var toggleWasFocused = $wrapper.find('.js-tabledrag-toggle-weight:focus').length; + $wrapper.empty().append($(Drupal.theme('tableDragToggle', 'show', Drupal.t('Show row weights'))).addClass('js-tabledrag-toggle-weight')); + if (toggleWasFocused) { + $wrapper.find('.js-tabledrag-toggle-weight').trigger('focus'); + } + }); + }, + showColumns: function showColumns() { + var $tables = $('table').findOnce('tabledrag'); + + $tables.find('.tabledrag-hide').css('display', ''); + + $tables.find('.js-tabledrag-handle').css('display', 'none'); + + $tables.find('.tabledrag-has-colspan').each(function increaseColspan() { + this.colSpan = this.colSpan + 1; + }); + + $('.js-tabledrag-toggle-weight-wrapper').each(function addHideWeightToggle() { + var $wrapper = $(this); + var toggleWasFocused = $wrapper.find('.js-tabledrag-toggle-weight:focus').length; + $wrapper.empty().append($(Drupal.theme('tableDragToggle', 'hide', Drupal.t('Hide row weights'))).addClass('js-tabledrag-toggle-weight')); + if (toggleWasFocused) { + $wrapper.find('.js-tabledrag-toggle-weight').trigger('focus'); + } + }); + }, + rowSettings: function rowSettings(group, row) { + var field = $(row).find('.' + group); + var tableSettingsGroup = this.tableSettings[group]; + return Object.keys(tableSettingsGroup).map(function (delta) { + var targetClass = tableSettingsGroup[delta].target; + var rowSettings = void 0; + if (field.is('.' + targetClass)) { + rowSettings = {}; + Object.keys(tableSettingsGroup[delta]).forEach(function (n) { + rowSettings[n] = tableSettingsGroup[delta][n]; + }); + } + return rowSettings; + }).filter(function (rowSetting) { + return rowSetting; + })[0]; + }, + makeDraggable: function makeDraggable(item) { + var self = this; + var $item = $(item); + var $firstCell = $item.find('td:first-of-type').wrapInner(Drupal.theme.tableDragCellContentWrapper()).wrapInner($(Drupal.theme('tableDragCellItemsWrapper')).addClass('js-tabledrag-cell-content')); + var $targetElem = $firstCell.find('.js-tabledrag-cell-content').length ? $firstCell.find('.js-tabledrag-cell-content') : $firstCell.addClass('js-tabledrag-cell-content'); + + $targetElem.find('.js-indentation').detach().prependTo($targetElem); + + $targetElem.find('a').addClass('menu-item__link'); + + var handle = $(Drupal.theme.tableDragHandle()).addClass('js-tabledrag-handle').attr('title', Drupal.t('Drag to re-order')); + + var $indentationLast = $targetElem.find('.js-indentation').eq(-1); + if ($indentationLast.length) { + $indentationLast.after(handle); + + self.indentCount = Math.max($item.find('.js-indentation').length, self.indentCount); + } else { + $targetElem.prepend(handle); + } + + handle.on('click', function (event) { + event.preventDefault(); + }); + + if (handle.closest('.js-tabledrag-disabled').length) { + return; + } + + handle.on('mousedown touchstart pointerdown', function (event) { + event.preventDefault(); + if (event.originalEvent.type === 'touchstart') { + event = event.originalEvent.touches[0]; + } + self.dragStart(event, self, item); + }); + + handle.on('focus', function () { + self.safeBlur = true; + }); + + handle.on('blur', function (event) { + if (self.rowObject && self.safeBlur) { + self.dropRow(event, self); + } + }); + + handle.on('keydown', function (event) { + if (event.keyCode !== 9 && !self.rowObject) { + self.rowObject = new self.row(item, 'keyboard', self.indentEnabled, self.maxDepth, true); + } + + var keyChange = false; + var groupHeight = void 0; + + switch (event.keyCode) { + case 37: + case 63234: + keyChange = true; + self.rowObject.indent(-1 * self.rtl); + break; + + case 38: + case 63232: + { + var $previousRow = $(self.rowObject.element).prev('tr').eq(0); + var previousRow = $previousRow.get(0); + while (previousRow && $previousRow.is(':hidden')) { + $previousRow = $(previousRow).prev('tr').eq(0); + previousRow = $previousRow.get(0); + } + if (previousRow) { + self.safeBlur = false; + self.rowObject.direction = 'up'; + keyChange = true; + + if ($(item).is('.tabledrag-root')) { + groupHeight = 0; + while (previousRow && $previousRow.find('.js-indentation').length) { + $previousRow = $(previousRow).prev('tr').eq(0); + previousRow = $previousRow.get(0); + groupHeight += $previousRow.is(':hidden') ? 0 : previousRow.offsetHeight; + } + if (previousRow) { + self.rowObject.swap('before', previousRow); + + window.scrollBy(0, -groupHeight); + } + } else if (self.table.tBodies[0].rows[0] !== previousRow || $previousRow.is('.draggable')) { + self.rowObject.swap('before', previousRow); + self.rowObject.interval = null; + self.rowObject.indent(0); + window.scrollBy(0, -parseInt(item.offsetHeight, 10)); + } + + handle.trigger('focus'); + } + break; + } + + case 39: + case 63235: + keyChange = true; + self.rowObject.indent(self.rtl); + break; + + case 40: + case 63233: + { + var $nextRow = $(self.rowObject.group).eq(-1).next('tr').eq(0); + var nextRow = $nextRow.get(0); + while (nextRow && $nextRow.is(':hidden')) { + $nextRow = $(nextRow).next('tr').eq(0); + nextRow = $nextRow.get(0); + } + if (nextRow) { + self.safeBlur = false; + self.rowObject.direction = 'down'; + keyChange = true; + + if ($(item).is('.tabledrag-root')) { + groupHeight = 0; + var nextGroup = new self.row(nextRow, 'keyboard', self.indentEnabled, self.maxDepth, false); + if (nextGroup) { + $(nextGroup.group).each(function groupIterator() { + groupHeight += $(this).is(':hidden') ? 0 : this.offsetHeight; + }); + var nextGroupRow = $(nextGroup.group).eq(-1).get(0); + self.rowObject.swap('after', nextGroupRow); + + window.scrollBy(0, parseInt(groupHeight, 10)); + } + } else { + self.rowObject.swap('after', nextRow); + self.rowObject.interval = null; + self.rowObject.indent(0); + window.scrollBy(0, parseInt(item.offsetHeight, 10)); + } + + handle.trigger('focus'); + } + break; + } + } + + if (self.rowObject && self.rowObject.changed === true) { + $(item).addClass('drag'); + if (self.oldRowElement) { + $(self.oldRowElement).removeClass('drag-previous'); + } + self.oldRowElement = item; + if (self.striping === true) { + self.restripeTable(); + } + self.onDrag(); + } + + if (keyChange) { + return false; + } + }); + + handle.on('keypress', function (event) { + + switch (event.keyCode) { + case 37: + case 38: + case 39: + case 40: + return false; + } + }); + }, + dragStart: function dragStart(event, self, item) { + self.dragObject = {}; + self.dragObject.initOffset = self.getPointerOffset(item, event); + self.dragObject.initPointerCoords = self.pointerCoords(event); + if (self.indentEnabled) { + self.dragObject.indentPointerPos = self.dragObject.initPointerCoords; + } + + if (self.rowObject) { + $(self.rowObject.element).find('.js-tabledrag-handle').trigger('blur'); + } + + self.rowObject = new self.row(item, 'pointer', self.indentEnabled, self.maxDepth, true); + + self.table.topY = $(self.table).offset().top; + self.table.bottomY = self.table.topY + self.table.offsetHeight; + + $(item).addClass('drag'); + + $('body').addClass('drag'); + if (self.oldRowElement) { + $(self.oldRowElement).removeClass('drag-previous'); + } + }, + dragRow: function dragRow(event, self) { + if (self.dragObject) { + self.currentPointerCoords = self.pointerCoords(event); + var y = self.currentPointerCoords.y - self.dragObject.initOffset.y; + var x = self.currentPointerCoords.x - self.dragObject.initOffset.x; + + if (y !== self.oldY) { + self.rowObject.direction = y > self.oldY ? 'down' : 'up'; + + self.oldY = y; + + var scrollAmount = self.checkScroll(self.currentPointerCoords.y); + + clearInterval(self.scrollInterval); + + if (scrollAmount > 0 && self.rowObject.direction === 'down' || scrollAmount < 0 && self.rowObject.direction === 'up') { + self.setScroll(scrollAmount); + } + + var currentRow = self.findDropTargetRow(x, y); + if (currentRow) { + if (self.rowObject.direction === 'down') { + self.rowObject.swap('after', currentRow, self); + } else { + self.rowObject.swap('before', currentRow, self); + } + if (self.striping === true) { + self.restripeTable(); + } + } + } + + if (self.indentEnabled) { + var xDiff = self.currentPointerCoords.x - self.dragObject.indentPointerPos.x; + + var indentDiff = Math.round(xDiff / self.indentAmount); + + var indentChange = self.rowObject.indent(indentDiff); + + self.dragObject.indentPointerPos.x += self.indentAmount * indentChange; + self.indentCount = Math.max(self.indentCount, self.rowObject.indents); + } + + return false; + } + }, + dropRow: function dropRow(event, self) { + var droppedRow = void 0; + var $droppedRow = void 0; + + if (self.rowObject !== null) { + droppedRow = self.rowObject.element; + $droppedRow = $(droppedRow); + + if (self.rowObject.changed === true) { + self.updateFields(droppedRow); + + Object.keys(self.tableSettings || {}).forEach(function (group) { + var rowSettings = self.rowSettings(group, droppedRow); + if (rowSettings.relationship === 'group') { + Object.keys(self.rowObject.children || {}).forEach(function (n) { + self.updateField(self.rowObject.children[n], group); + }); + } + }); + + self.rowObject.markChanged(); + if (self.changed === false) { + var $messageTarget = $(self.table).prevAll('.js-tabledrag-toggle-weight-wrapper').length ? $(self.table).prevAll('.js-tabledrag-toggle-weight-wrapper').last() : self.table; + $(Drupal.theme('tableDragChangedWarning')).insertBefore($messageTarget).hide().fadeIn('slow'); + self.changed = true; + } + } + + if (self.indentEnabled) { + self.rowObject.removeIndentClasses(); + } + if (self.oldRowElement) { + $(self.oldRowElement).removeClass('drag-previous'); + } + $droppedRow.removeClass('drag').addClass('drag-previous'); + self.oldRowElement = droppedRow; + self.onDrop(); + self.rowObject = null; + } + + if (self.dragObject !== null) { + self.dragObject = null; + $('body').removeClass('drag'); + clearInterval(self.scrollInterval); + } + }, + pointerCoords: function pointerCoords(event) { + if (event.pageX || event.pageY) { + return { x: event.pageX, y: event.pageY }; + } + return { + x: event.clientX + (document.body.scrollLeft - document.body.clientLeft), + y: event.clientY + (document.body.scrollTop - document.body.clientTop) + }; + }, + getPointerOffset: function getPointerOffset(target, event) { + var docPos = $(target).offset(); + var pointerPos = this.pointerCoords(event); + return { x: pointerPos.x - docPos.left, y: pointerPos.y - docPos.top }; + }, + findDropTargetRow: function findDropTargetRow(x, y) { + var _this3 = this; + + var rows = $(this.table.tBodies[0].rows).not(':hidden'); + + var _loop = function _loop(n) { + var row = rows[n]; + var $row = $(row); + var rowY = $row.offset().top; + var rowHeight = void 0; + + if (row.offsetHeight === 0) { + rowHeight = parseInt(row.firstChild.offsetHeight, 10) / 2; + } else { + rowHeight = parseInt(row.offsetHeight, 10) / 2; + } + + if (y > rowY - rowHeight && y < rowY + rowHeight) { + if (_this3.indentEnabled) { + if (Object.keys(_this3.rowObject.group).some(function (o) { + return _this3.rowObject.group[o] === row; + })) { + return { + v: null + }; + } + } else if (row === _this3.rowObject.element) { + return { + v: null + }; + } + + if (!_this3.rowObject.isValidSwap(row)) { + return { + v: null + }; + } + + while ($row.is(':hidden') && $row.prev('tr').is(':hidden')) { + $row = $row.prev('tr:first-of-type'); + row = $row.get(0); + } + return { + v: row + }; + } + }; + + for (var n = 0; n < rows.length; n++) { + var _ret = _loop(n); + + if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; + } + return null; + }, + updateFields: function updateFields(changedRow) { + var _this4 = this; + + Object.keys(this.tableSettings || {}).forEach(function (group) { + _this4.updateField(changedRow, group); + }); + }, + updateField: function updateField(changedRow, group) { + var rowSettings = this.rowSettings(group, changedRow); + var $changedRow = $(changedRow); + var sourceRow = void 0; + var $previousRow = void 0; + var previousRow = void 0; + var useSibling = void 0; + + if (rowSettings.relationship === 'self' || rowSettings.relationship === 'group') { + sourceRow = changedRow; + } else if (rowSettings.relationship === 'sibling') { + $previousRow = $changedRow.prev('tr:first-of-type'); + previousRow = $previousRow.get(0); + var $nextRow = $changedRow.next('tr:first-of-type'); + var nextRow = $nextRow.get(0); + sourceRow = changedRow; + if ($previousRow.is('.draggable') && $previousRow.find('.' + group).length) { + if (this.indentEnabled) { + if ($previousRow.find('.js-indentations').length === $changedRow.find('.js-indentations').length) { + sourceRow = previousRow; + } + } else { + sourceRow = previousRow; + } + } else if ($nextRow.is('.draggable') && $nextRow.find('.' + group).length) { + if (this.indentEnabled) { + if ($nextRow.find('.js-indentations').length === $changedRow.find('.js-indentations').length) { + sourceRow = nextRow; + } + } else { + sourceRow = nextRow; + } + } + } else if (rowSettings.relationship === 'parent') { + $previousRow = $changedRow.prev('tr'); + previousRow = $previousRow; + while ($previousRow.length && $previousRow.find('.js-indentation').length >= this.rowObject.indents) { + $previousRow = $previousRow.prev('tr'); + previousRow = $previousRow; + } + + if ($previousRow.length) { + sourceRow = $previousRow.get(0); + } else { + sourceRow = $(this.table).find('tr.draggable:first-of-type').get(0); + if (sourceRow === this.rowObject.element) { + sourceRow = $(this.rowObject.group[this.rowObject.group.length - 1]).next('tr.draggable').get(0); + } + useSibling = true; + } + } + + this.copyDragClasses(sourceRow, changedRow, group); + rowSettings = this.rowSettings(group, changedRow); + + if (useSibling) { + rowSettings.relationship = 'sibling'; + rowSettings.source = rowSettings.target; + } + + var targetClass = '.' + rowSettings.target; + var targetElement = $changedRow.find(targetClass).get(0); + + if (targetElement) { + var sourceClass = '.' + rowSettings.source; + var sourceElement = $(sourceClass, sourceRow).get(0); + switch (rowSettings.action) { + case 'depth': + targetElement.value = $(sourceElement).closest('tr').find('.js-indentation').length; + break; + + case 'match': + targetElement.value = sourceElement.value; + break; + + case 'order': + { + var siblings = this.rowObject.findSiblings(rowSettings); + if ($(targetElement).is('select')) { + var values = []; + $(targetElement).find('option').each(function collectValues() { + values.push(this.value); + }); + var maxVal = values[values.length - 1]; + + $(siblings).find(targetClass).each(function assignValues() { + if (values.length > 0) { + this.value = values.shift(); + } else { + this.value = maxVal; + } + }); + } else { + var weight = parseInt($(siblings[0]).find(targetClass).val(), 10) || 0; + $(siblings).find(targetClass).each(function assignWeight() { + this.value = weight; + weight += 1; + }); + } + break; + } + } + } + }, + copyDragClasses: function copyDragClasses(sourceRow, targetRow, group) { + var sourceElement = $(sourceRow).find('.' + group); + var targetElement = $(targetRow).find('.' + group); + if (sourceElement.length && targetElement.length) { + targetElement[0].className = sourceElement[0].className; + } + }, + checkScroll: function checkScroll(cursorY) { + var de = document.documentElement; + var b = document.body; + var windowHeight = window.innerHeight || (de.clientHeight && de.clientWidth !== 0 ? de.clientHeight : b.offsetHeight); + this.windowHeight = windowHeight; + var scrollY = void 0; + if (document.all) { + scrollY = !de.scrollTop ? b.scrollTop : de.scrollTop; + } else { + scrollY = window.pageYOffset ? window.pageYOffset : window.scrollY; + } + this.scrollY = scrollY; + var trigger = this.scrollSettings.trigger; + + var delta = 0; + + if (cursorY - scrollY > windowHeight - trigger) { + delta = trigger / (windowHeight + (scrollY - cursorY)); + delta = delta > 0 && delta < trigger ? delta : trigger; + return delta * this.scrollSettings.amount; + } + if (cursorY - scrollY < trigger) { + delta = trigger / (cursorY - scrollY); + delta = delta > 0 && delta < trigger ? delta : trigger; + return -delta * this.scrollSettings.amount; + } + }, + setScroll: function setScroll(scrollAmount) { + var self = this; + + this.scrollInterval = setInterval(function () { + self.checkScroll(self.currentPointerCoords.y); + var aboveTable = self.scrollY > self.table.topY; + var belowTable = self.scrollY + self.windowHeight < self.table.bottomY; + if (scrollAmount > 0 && belowTable || scrollAmount < 0 && aboveTable) { + window.scrollBy(0, scrollAmount); + } + }, this.scrollSettings.interval); + }, + restripeTable: function restripeTable() { + $(this.table).find('> tbody > tr.draggable, > tr.draggable').filter(':visible').filter(':odd').removeClass('odd').addClass('even').end().filter(':even').removeClass('even').addClass('odd'); + }, + onDrag: function onDrag() { + return null; + }, + onDrop: function onDrop() { + return null; + }, + row: function row(tableRow, method, indentEnabled, maxDepth, addClasses) { + var $tableRow = $(tableRow); + + this.element = tableRow; + this.method = method; + this.group = [tableRow]; + this.groupDepth = $tableRow.find('.js-indentation').length; + this.changed = false; + this.table = $tableRow.closest('table')[0]; + this.indentEnabled = indentEnabled; + this.maxDepth = maxDepth; + + this.direction = ''; + if (this.indentEnabled) { + this.indents = $tableRow.find('.js-indentation').length; + this.children = this.findChildren(addClasses); + this.group = $.merge(this.group, this.children); + + for (var n = 0; n < this.group.length; n++) { + this.groupDepth = Math.max($(this.group[n]).find('.js-indentation').length, this.groupDepth); + } + } + } + }); + + $.extend(Drupal.tableDrag.prototype.row.prototype, { + findChildren: function findChildren(addClasses) { + var parentIndentation = this.indents; + var currentRow = $(this.element, this.table).next('tr.draggable'); + var rows = []; + var child = 0; + + function rowIndentation(indentNum, el) { + var self = $(el); + if (child === 1 && indentNum === parentIndentation) { + self.addClass('tree-child-first'); + } + if (indentNum === parentIndentation) { + self.addClass('tree-child'); + } else if (indentNum > parentIndentation) { + self.addClass('tree-child-horizontal'); + } + } + + while (currentRow.length) { + if (currentRow.find('.js-indentation').length > parentIndentation) { + child += 1; + rows.push(currentRow[0]); + if (addClasses) { + currentRow.find('.js-indentation').each(rowIndentation); + } + } else { + break; + } + currentRow = currentRow.next('tr.draggable'); + } + if (addClasses && rows.length) { + $(rows[rows.length - 1]).find('.js-indentation:nth-child(' + (parentIndentation + 1) + ')').addClass('tree-child-last'); + } + return rows; + }, + isValidSwap: function isValidSwap(row) { + var $row = $(row); + if (this.indentEnabled) { + var prevRow = void 0; + var nextRow = void 0; + if (this.direction === 'down') { + prevRow = row; + nextRow = $row.next('tr').get(0); + } else { + prevRow = $row.prev('tr').get(0); + nextRow = row; + } + this.interval = this.validIndentInterval(prevRow, nextRow); + + if (this.interval.min > this.interval.max) { + return false; + } + } + + if (this.table.tBodies[0].rows[0] === row && $row.is(':not(.draggable)')) { + return false; + } + + return true; + }, + swap: function swap(position, row) { + this.group.forEach(function (detachedRow) { + Drupal.detachBehaviors(detachedRow, drupalSettings, 'move'); + }); + $(row)[position](this.group); + + this.group.forEach(function (attachedRow) { + Drupal.attachBehaviors(attachedRow, drupalSettings); + }); + this.changed = true; + this.onSwap(row); + }, + validIndentInterval: function validIndentInterval(prevRow, nextRow) { + var $prevRow = $(prevRow); + var maxIndent = void 0; + + var minIndent = nextRow ? $(nextRow).find('.js-indentation').length : 0; + + if (!prevRow || $prevRow.is(':not(.draggable)') || $(this.element).is('.tabledrag-root')) { + maxIndent = 0; + } else { + maxIndent = $prevRow.find('.js-indentation').length + ($prevRow.is('.tabledrag-leaf') ? 0 : 1); + + if (this.maxDepth) { + maxIndent = Math.min(maxIndent, this.maxDepth - (this.groupDepth - this.indents)); + } + } + + return { min: minIndent, max: maxIndent }; + }, + indent: function indent(indentDiff) { + var $group = $(this.group); + + if (!this.interval) { + var prevRow = $(this.element).prev('tr').get(0); + var nextRow = $group.eq(-1).next('tr').get(0); + this.interval = this.validIndentInterval(prevRow, nextRow); + } + + var indent = this.indents + indentDiff; + indent = Math.max(indent, this.interval.min); + indent = Math.min(indent, this.interval.max); + indentDiff = indent - this.indents; + + for (var n = 1; n <= Math.abs(indentDiff); n++) { + if (indentDiff < 0) { + $group.find('.js-indentation:first-of-type').remove(); + this.indents -= 1; + } else { + $group.find('.js-tabledrag-cell-content').prepend(Drupal.theme('tableDragIndentation')); + this.indents += 1; + } + } + if (indentDiff) { + this.changed = true; + this.groupDepth += indentDiff; + this.onIndent(); + } + + return indentDiff; + }, + findSiblings: function findSiblings(rowSettings) { + var siblings = []; + var directions = ['prev', 'next']; + var rowIndentation = this.indents; + var checkRowIndentation = void 0; + for (var d = 0; d < directions.length; d++) { + var checkRow = $(this.element)[directions[d]](); + while (checkRow.length) { + if (checkRow.find('.' + rowSettings.target)) { + if (this.indentEnabled) { + checkRowIndentation = checkRow.find('.js-indentation').length; + } + + if (!this.indentEnabled || checkRowIndentation === rowIndentation) { + siblings.push(checkRow[0]); + } else if (checkRowIndentation < rowIndentation) { + break; + } + } else { + break; + } + checkRow = checkRow[directions[d]](); + } + + if (directions[d] === 'prev') { + siblings.reverse(); + siblings.push(this.element); + } + } + return siblings; + }, + removeIndentClasses: function removeIndentClasses() { + var _this5 = this; + + Object.keys(this.children || {}).forEach(function (n) { + $(_this5.children[n]).find('.js-indentation').removeClass('tree-child').removeClass('tree-child-first').removeClass('tree-child-last').removeClass('tree-child-horizontal'); + }); + }, + markChanged: function markChanged() { + var marker = $(Drupal.theme('tableDragChangedMarker')).addClass('js-tabledrag-changed-marker'); + var cell = $(this.element).find('td:first-of-type'); + if (cell.find('.js-tabledrag-changed-marker').length === 0) { + cell.find('.js-tabledrag-handle').after(marker); + } + }, + onIndent: function onIndent() { + return null; + }, + onSwap: function onSwap(swappedRow) { + return null; + } + }); + + $.extend(Drupal.theme, { + tableDragChangedMarker: function tableDragChangedMarker() { + return '*'; + }, + tableDragIndentation: function tableDragIndentation() { + return '
    '; + }, + tableDragChangedWarning: function tableDragChangedWarning() { + return ''; + }, + tableDragHandle: function tableDragHandle() { + return ''; + }, + tableDragCellItemsWrapper: function tableDragCellItemsWrapper() { + return '
    '; + }, + tableDragCellContentWrapper: function tableDragCellContentWrapper() { + return '
    '; + }, + tableDragToggle: function tableDragToggle(action, text) { + var classes = ['action-link', 'action-link--extrasmall', 'tabledrag-toggle-weight']; + switch (action) { + case 'show': + classes.push('action-link--icon-show'); + break; + + default: + classes.push('action-link--icon-hide'); + break; + } + + return '' + text + ''; + }, + tableDragToggleWrapper: function tableDragToggleWrapper() { + return '
    '; + } + }); +})(jQuery, Drupal, drupalSettings); \ No newline at end of file diff --git a/core/themes/claro/js/user.es6.js b/core/themes/claro/js/user.es6.js new file mode 100644 index 0000000000..80229ac52b --- /dev/null +++ b/core/themes/claro/js/user.es6.js @@ -0,0 +1,344 @@ +/** + * @file + * Overrides Drupal core user.js that provides password strength indicator. + * + * @todo remove these overrides after + * https://www.drupal.org/project/drupal/issues/3067523 has been resolved. + */ + +(($, Drupal) => { + /** + * This overrides the default Drupal.behaviors.password functionality. + * + * - Markup has been moved to theme functions so that to enable customizations + * needed for matching Claro's design requirements + * (https://www.drupal.org/project/drupal/issues/3067523). + * - Modified classes so that same class names are not being used for different + * elements (https://www.drupal.org/project/drupal/issues/3061265). + */ + Drupal.behaviors.password = { + attach(context, settings) { + const $passwordInput = $(context) + .find('input.js-password-field') + .once('password'); + + if ($passwordInput.length) { + // Settings and translated messages added by + // user_form_process_password_confirm(). + const translate = settings.password; + + // The form element object of the password input. + const $passwordInputParent = $passwordInput.parent(); + + // The password_confirm form element object. + const $passwordWidget = $passwordInput.closest( + '.js-form-type-password-confirm', + ); + + // The password confirm input. + const $passwordConfirmInput = $passwordWidget.find( + 'input.js-password-confirm', + ); + + // The strength feedback element for the password input. + const $passwordInputHelp = $( + Drupal.theme.passwordInputHelp(translate.strengthTitle), + ); + + // The password match feedback for the password confirm input. + const $passwordConfirmHelp = $( + Drupal.theme.passwordConfirmHelp(translate.confirmTitle), + ); + + const $passwordInputStrengthBar = $passwordInputHelp.find( + '.js-password-strength-bar', + ); + const $passwordInputStrengthMessageWrapper = $passwordInputHelp.find( + '.js-password-strength-text', + ); + const $passwordConfirmMatch = $passwordConfirmHelp.find( + '.js-password-match-text', + ); + let $passwordSuggestionsTips = $( + Drupal.theme.passwordSuggestionsTips('', ''), + ).hide(); + + // If the password strength indicator is enabled, add its markup. + if (settings.password.showStrengthIndicator) { + $passwordConfirmInput + .after($passwordConfirmHelp) + .parent() + .after($passwordSuggestionsTips); + + $passwordInputParent.append($passwordInputHelp); + } + + // Check that password and confirmation inputs match. + const passwordCheckMatch = confirmInputVal => { + if (confirmInputVal) { + const success = $passwordInput.val() === confirmInputVal; + const confirmClass = success ? 'ok' : 'error'; + const confirmMatchMessage = success + ? translate.confirmSuccess + : translate.confirmFailure; + + // Update the success message and set the class accordingly if + // needed. + if ( + !$passwordConfirmMatch.hasClass(confirmClass) || + !$passwordConfirmMatch.html() === confirmMatchMessage + ) { + $passwordConfirmMatch + .html(confirmMatchMessage) + .removeClass('ok error') + .addClass(confirmClass); + } + } + }; + + // Check the password strength. + const passwordCheck = () => { + if (settings.password.showStrengthIndicator) { + // Evaluate the password strength. + const result = Drupal.evaluatePasswordStrength( + $passwordInput.val(), + settings.password, + ); + const $newSuggestions = $( + Drupal.theme.passwordSuggestionsTips( + translate.hasWeaknesses, + result.tips, + ), + ); + + // Update the suggestions for how to improve the password. + if ($newSuggestions.html() !== $passwordSuggestionsTips.html()) { + $passwordSuggestionsTips.replaceWith($newSuggestions); + $passwordSuggestionsTips = $newSuggestions; + + // Only show the description box if a weakness exists in the + // password. + $passwordSuggestionsTips.toggle(result.strength !== 100); + } + + // Adjust the length of the strength indicator. + $passwordInputStrengthBar + .css('width', `${result.strength}%`) + .removeClass('is-weak is-fair is-good is-strong') + .addClass(result.indicatorClass); + + // Update the strength indication text if needed. + if ( + !$passwordInputStrengthMessageWrapper.hasClass( + result.indicatorClass, + ) || + !$passwordInputStrengthMessageWrapper.html() === + result.indicatorText + ) { + $passwordInputStrengthMessageWrapper + .html(result.indicatorText) + .removeClass('is-weak is-fair is-good is-strong') + .addClass(result.indicatorClass); + } + } + + $passwordWidget + .removeClass('is-initial') + .removeClass('is-password-empty is-password-filled') + .removeClass('is-confirm-empty is-confirm-filled'); + + // Check the value of the password input and add the proper classes. + $passwordWidget.addClass( + $passwordInput.val() ? 'is-password-filled' : 'is-password-empty', + ); + + // Check the value in the confirm input and show results. + passwordCheckMatch($passwordConfirmInput.val()); + $passwordWidget.addClass( + $passwordConfirmInput.val() + ? 'is-confirm-filled' + : 'is-confirm-empty', + ); + }; + + // Add initial classes. + $passwordWidget + .addClass( + $passwordInput.val() ? 'is-password-filled' : 'is-password-empty', + ) + .addClass( + $passwordConfirmInput.val() + ? 'is-confirm-filled' + : 'is-confirm-empty', + ); + + // Monitor input events. + $passwordInput.on('input', passwordCheck); + $passwordConfirmInput.on('input', passwordCheck); + } + }, + }; + + /** + * Override the default Drupal.evaluatePasswordStrength. + * + * The default implementation of this function hard codes some markup inside + * this function. Rendering markup is now handled by + * Drupal.behaviors.password. + * + * @param {string} password + * Password to evaluate the strength. + * + * @param {Array.} translate + * Settings and translated messages added by user_form_process_password_confirm(). + * + * @return {Array.} + * Array containing the strength, tips, indicators text and class. + */ + Drupal.evaluatePasswordStrength = (password, translate) => { + password = password.trim(); + let indicatorText; + let indicatorClass; + let weaknesses = 0; + let strength = 100; + const tips = []; + const hasLowercase = /[a-z]/.test(password); + const hasUppercase = /[A-Z]/.test(password); + const hasNumbers = /[0-9]/.test(password); + const hasPunctuation = /[^a-zA-Z0-9]/.test(password); + + // If there is a username edit box on the page, compare password to that, + // otherwise use value from the database. + const $usernameBox = $('input.username'); + const username = + $usernameBox.length > 0 ? $usernameBox.val() : translate.username; + + // Lose 5 points for every character less than 12, plus a 30 point penalty. + if (password.length < 12) { + tips.push(translate.tooShort); + strength -= (12 - password.length) * 5 + 30; + } + + // Count weaknesses. + if (!hasLowercase) { + tips.push(translate.addLowerCase); + weaknesses += 1; + } + if (!hasUppercase) { + tips.push(translate.addUpperCase); + weaknesses += 1; + } + if (!hasNumbers) { + tips.push(translate.addNumbers); + weaknesses += 1; + } + if (!hasPunctuation) { + tips.push(translate.addPunctuation); + weaknesses += 1; + } + + // Apply penalty for each weakness (balanced against length penalty). + switch (weaknesses) { + case 1: + strength -= 12.5; + break; + + case 2: + strength -= 25; + break; + + case 3: + strength -= 40; + break; + + case 4: + strength -= 40; + break; + + default: + // Default: 0. Nothing to do. + break; + } + + // Check if password is the same as the username. + if (password !== '' && password.toLowerCase() === username.toLowerCase()) { + tips.push(translate.sameAsUsername); + // Passwords the same as username are always very weak. + strength = 5; + } + + // Based on the strength, work out what text should be shown by the + // password strength meter. + if (strength < 60) { + indicatorText = translate.weak; + indicatorClass = 'is-weak'; + } else if (strength < 70) { + indicatorText = translate.fair; + indicatorClass = 'is-fair'; + } else if (strength < 80) { + indicatorText = translate.good; + indicatorClass = 'is-good'; + } else if (strength <= 100) { + indicatorText = translate.strong; + indicatorClass = 'is-strong'; + } + + return { + strength, + tips, + indicatorText, + indicatorClass, + }; + }; + + /** + * Password strenght feedback for password confirm's main input. + * + * @param {string} message + * The prefix text for the strength feedback word. + * + * @return {string} + * The string representing the DOM fragment. + */ + Drupal.theme.passwordInputHelp = message => + `
    +
    +
    +
    +
    + ${message} +
    +
    `; + + /** + * Password match feedback for password confirm input. + * + * @param {string} message + * The message that precedes the yes|no text. + * + * @return {string} + * A string representing the DOM fragment. + */ + Drupal.theme.passwordConfirmHelp = message => + `