diff --git a/src/Element/ComponentForm.php b/src/Element/ComponentForm.php index 220bbc0dc964067c376f9c1a86d8756348e93d02..9db7bc23b2ff666dbd11e0ead371f1bda8fa5f93 100644 --- a/src/Element/ComponentForm.php +++ b/src/Element/ComponentForm.php @@ -263,11 +263,51 @@ class ComponentForm extends ComponentFormBase { return $sub_form; } + /** + * Open wrapped elements with errors. + * + * @param array $element + * The element. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @return bool + * return TRUE if errors were found. + */ + protected static function openWrappedElementsWithErrors(array &$element, FormStateInterface $form_state) : bool { + $errors = $form_state->getErrors(); + if (count($errors) === 0) { + return FALSE; + } + $element_name = implode("][", $element["#parents"]); + $error_elements_found = FALSE; + foreach (array_keys($errors) as $error_name) { + if (!str_starts_with($error_name, $element_name)) { + continue; + } + $error_elements_found = TRUE; + $parents = array_slice(explode("][", $error_name), count($element["#parents"])); + if (count($parents) < 2) { + continue; + } + $parents_of_prop_or_slot = array_slice($parents, 0, 2); + $prop_or_slot = NestedArray::getValue($element, $parents_of_prop_or_slot); + if (!empty($prop_or_slot) && isset($prop_or_slot["#wrap"]) && $prop_or_slot["#wrap"]) { + $parents_of_prop_or_slot[] = $parents_of_prop_or_slot[1]; + $parents_of_prop_or_slot[] = "#open"; + NestedArray::setValue($element, $parents_of_prop_or_slot, TRUE); + } + } + return $error_elements_found; + } + /** * Form element validation handler. */ public static function validateFormElement(array &$element, FormStateInterface $form_state) : void { - + if (static::openWrappedElementsWithErrors($element, $form_state)) { + return; + } try { $trigger_element = $form_state->getTriggeringElement(); if (isset($trigger_element['#ui_patterns']) === FALSE) { diff --git a/src/Plugin/UiPatterns/Source/AttributesWidget.php b/src/Plugin/UiPatterns/Source/AttributesWidget.php index 6c0581292be69d16b2663e74686615b1c170bbf5..cb2d64af7a3edd6abaf03c11b2e71f49b776aab8 100644 --- a/src/Plugin/UiPatterns/Source/AttributesWidget.php +++ b/src/Plugin/UiPatterns/Source/AttributesWidget.php @@ -64,13 +64,15 @@ class AttributesWidget extends SourcePluginBase { protected function buildRegexPattern(): string { // Attribute names are a mix of ASCII lower and upper alphas. $attr_name = "[a-zA-Z\-]+"; - // Allow anything in attributes values, which are between double quotes. - $double_quoted_value = '"[\s\w\-]*"'; + // Discard double quotes which are used for delimiting. + $double_quoted_value = '[^"]*'; $space = "\s*"; - $attr = $attr_name . "=" . $double_quoted_value . $space; - // The pattern must match the entire input's value, rather than matching a - // substring - as if a ^(?: were implied at the start of the pattern and )$ - // at the end. + $attr = sprintf("%s=\"%s\"%s", $attr_name, $double_quoted_value, $space); + // Start and end delimiters are not expected here, they will be added: + // - by \Drupal\Core\Render\Element\FormElementBase::validatePattern for + // server side validation + // - in the HTML5 pattern attribute, for client side validation + // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern return $space . "(" . $attr . ")*"; }