diff --git a/src/core/annotation.js b/src/core/annotation.js index bcad74973..d39e5dc58 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -1363,6 +1363,7 @@ class WidgetAnnotation extends Annotation { } data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY); + data.required = this.hasFieldFlag(AnnotationFieldFlag.REQUIRED); data.hidden = this._hasFlag(data.annotationFlags, AnnotationFlag.HIDDEN); } diff --git a/src/core/xfa/template.js b/src/core/xfa/template.js index c3f0f65cc..8bc4659de 100644 --- a/src/core/xfa/template.js +++ b/src/core/xfa/template.js @@ -204,6 +204,10 @@ function* getContainedChildren(node) { } } +function isRequired(node) { + return node.validate && node.validate.nullTest === "error"; +} + function setTabIndex(node) { while (node) { if (!node.traversal) { @@ -1368,6 +1372,7 @@ class CheckButton extends XFAObject { xfaOn: exportedValue.on, xfaOff: exportedValue.off, "aria-label": ariaLabel(field), + "aria-required": false, }, }; @@ -1375,6 +1380,11 @@ class CheckButton extends XFAObject { input.attributes.name = groupId; } + if (isRequired(field)) { + input.attributes["aria-required"] = true; + input.attributes.required = true; + } + return HTMLResult.success({ name: "label", attributes: { @@ -1465,8 +1475,14 @@ class ChoiceList extends XFAObject { dataId: (field[$data] && field[$data][$uid]) || field[$uid], style, "aria-label": ariaLabel(field), + "aria-required": false, }; + if (isRequired(field)) { + selectAttributes["aria-required"] = true; + selectAttributes.required = true; + } + if (this.open === "multiSelect") { selectAttributes.multiple = true; } @@ -1704,9 +1720,15 @@ class DateTimeEdit extends XFAObject { class: ["xfaTextfield"], style, "aria-label": ariaLabel(field), + "aria-required": false, }, }; + if (isRequired(field)) { + html.attributes["aria-required"] = true; + html.attributes.required = true; + } + return HTMLResult.success({ name: "label", attributes: { @@ -3859,9 +3881,15 @@ class NumericEdit extends XFAObject { class: ["xfaTextfield"], style, "aria-label": ariaLabel(field), + "aria-required": false, }, }; + if (isRequired(field)) { + html.attributes["aria-required"] = true; + html.attributes.required = true; + } + return HTMLResult.success({ name: "label", attributes: { @@ -5833,6 +5861,7 @@ class TextEdit extends XFAObject { class: ["xfaTextfield"], style, "aria-label": ariaLabel(field), + "aria-required": false, }, }; } else { @@ -5845,10 +5874,16 @@ class TextEdit extends XFAObject { class: ["xfaTextfield"], style, "aria-label": ariaLabel(field), + "aria-required": false, }, }; } + if (isRequired(field)) { + html.attributes["aria-required"] = true; + html.attributes.required = true; + } + return HTMLResult.success({ name: "label", attributes: { diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 32ec62641..00aca1a4c 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -343,11 +343,7 @@ class AnnotationElement { } }, required: event => { - if (event.detail.required) { - event.target.setAttribute("required", ""); - } else { - event.target.removeAttribute("required"); - } + this._setRequired(event.target, event.detail.required); }, bgColor: event => { setColor("bgColor", "backgroundColor", event); @@ -944,6 +940,15 @@ class WidgetAnnotationElement extends AnnotationElement { style.textAlign = TEXT_ALIGNMENT[this.data.textAlignment]; } } + + _setRequired(element, isRequired) { + if (isRequired) { + element.setAttribute("required", true); + } else { + element.removeAttribute("required"); + } + element.setAttribute("aria-required", isRequired); + } } class TextWidgetAnnotationElement extends WidgetAnnotationElement { @@ -1010,6 +1015,8 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { elementData.userValue = textContent; element.setAttribute("id", id); + this._setRequired(element, this.data.required); + element.addEventListener("input", event => { storage.setValue(id, { value: event.target.value }); this.setPropertyOnSiblings( @@ -1255,6 +1262,7 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement { const element = document.createElement("input"); GetElementsByNameSet.add(element); element.disabled = data.readOnly; + this._setRequired(element, this.data.required); element.type = "checkbox"; element.name = data.fieldName; if (value) { @@ -1338,6 +1346,7 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement { const element = document.createElement("input"); GetElementsByNameSet.add(element); element.disabled = data.readOnly; + this._setRequired(element, this.data.required); element.type = "radio"; element.name = data.fieldName; if (value) { @@ -1447,6 +1456,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { const selectElement = document.createElement("select"); GetElementsByNameSet.add(selectElement); selectElement.disabled = this.data.readOnly; + this._setRequired(selectElement, this.data.required); selectElement.name = this.data.fieldName; selectElement.setAttribute("id", id); selectElement.tabIndex = DEFAULT_TAB_INDEX; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 14b686d3c..9fa9ec66c 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -525,4 +525,4 @@ !issue14862.pdf !issue14705.pdf !bug1771477.pdf - +!bug1724918.pdf diff --git a/test/pdfs/bug1724918.pdf b/test/pdfs/bug1724918.pdf new file mode 100755 index 000000000..3652bb585 Binary files /dev/null and b/test/pdfs/bug1724918.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 8215e0966..f1b394a8e 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -6553,5 +6553,12 @@ "link": true, "type": "eq", "annotations": true + }, + { "id": "bug1724918", + "file": "pdfs/bug1724918.pdf", + "md5": "ab30269570c8ff2c9f8eb73fb376a081", + "rounds": 1, + "type": "eq", + "annotations": true } ] diff --git a/web/annotation_layer_builder.css b/web/annotation_layer_builder.css index 68172ac6f..17f796188 100644 --- a/web/annotation_layer_builder.css +++ b/web/annotation_layer_builder.css @@ -17,6 +17,16 @@ --annotation-unfocused-field-background: url("data:image/svg+xml;charset=UTF-8,"); } +@media (forced-colors: active) { + .annotationLayer .textWidgetAnnotation input:required, + .annotationLayer .textWidgetAnnotation textarea:required, + .annotationLayer .choiceWidgetAnnotation select:required, + .annotationLayer .buttonWidgetAnnotation.checkBox input:required, + .annotationLayer .buttonWidgetAnnotation.radioButton input:required { + outline: 1.5px solid selectedItem; + } +} + .annotationLayer section { position: absolute; text-align: initial; @@ -66,6 +76,14 @@ width: 100%; } +.annotationLayer .textWidgetAnnotation input:required, +.annotationLayer .textWidgetAnnotation textarea:required, +.annotationLayer .choiceWidgetAnnotation select:required, +.annotationLayer .buttonWidgetAnnotation.checkBox input:required, +.annotationLayer .buttonWidgetAnnotation.radioButton input:required { + outline: 1.5px solid red; +} + .annotationLayer .choiceWidgetAnnotation select option { padding: 0; } @@ -116,7 +134,7 @@ .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before, .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after, .annotationLayer .buttonWidgetAnnotation.radioButton input:checked:before { - background-color: rgba(0, 0, 0, 1); + background-color: CanvasText; content: ""; display: block; position: absolute; diff --git a/web/xfa_layer_builder.css b/web/xfa_layer_builder.css index ab1a911cf..8c97c84c2 100644 --- a/web/xfa_layer_builder.css +++ b/web/xfa_layer_builder.css @@ -17,6 +17,12 @@ --xfa-unfocused-field-background: url("data:image/svg+xml;charset=UTF-8,"); } +@media (forced-colors: active) { + .xfaLayer *:required { + outline: 1.5px solid selectedItem; + } +} + .xfaLayer .highlight { margin: -1px; padding: 1px; @@ -87,6 +93,10 @@ line-height: inherit; } +.xfaLayer *:required { + outline: 1.5px solid red; +} + .xfaLayer div { pointer-events: none; }