-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Issue 9100 #9116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Issue 9100 #9116
Changes from all commits
d5c3103
acbd33d
91be0c3
771cb45
01c3161
31e0438
6cfaf8a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,19 +19,32 @@ function describe(p5, fn) { | |
| * | ||
| * The first parameter, `text`, is the description of the canvas. | ||
| * | ||
| * The second parameter, `display`, is optional. It determines how the | ||
| * description is displayed. If `LABEL` is passed, as in | ||
| * `describe('A description.', LABEL)`, the description will be visible in | ||
| * a div element next to the canvas. If `FALLBACK` is passed, as in | ||
| * `describe('A description.', FALLBACK)`, the description will only be | ||
| * visible to screen readers. This is the default mode. | ||
| * The second parameter, `langOrDisplay`, is optional. It can either | ||
| * determine the language of the description or how the description | ||
| * is displayed. The description can be displayed with either `LABEL` or `FALLBACK`. | ||
| * - If a lang is passed, as in `describe('A description.', 'en')`, | ||
| * the description will be read by the screen reader using the | ||
| * specified language's voice. The screen reader must have the specified | ||
| * language's voice installed for this to work. | ||
| * - If `LABEL` is passed, as in `describe('A description.', LABEL)`, | ||
| * the description will be visible in a div element next to the canvas. | ||
| * - If `FALLBACK` is passed, as in `describe('A description.', FALLBACK)`, | ||
| * the description will only be visible to screen readers. FALLBACK is | ||
| * the default mode. | ||
| * | ||
| * The third parameter, `display`, is optional but is only used if the second | ||
| * parameter is lang, as in the language of the description, and the user wants | ||
| * to determine how the description is displayed as well. In this case, they can | ||
| * pass either `LABEL` or `FALLBACK` as the third parameter. | ||
| * | ||
| * | ||
| * Read | ||
| * <a href="/learn/accessible-labels.html">Writing accessible canvas descriptions</a> | ||
| * to learn more about making sketches accessible. | ||
| * | ||
| * @method describe | ||
| * @param {String} text description of the canvas. | ||
| * @param {(FALLBACK|LABEL|String)} [langOrDisplay] valid lang attribute or either LABEL or FALLBACK. | ||
| * @param {(FALLBACK|LABEL)} [display] either LABEL or FALLBACK. | ||
| * | ||
| * @example | ||
|
|
@@ -110,18 +123,22 @@ function describe(p5, fn) { | |
| * describe(`A green circle at (${x}, 50) moves from left to right on a gray square.`, LABEL); | ||
| * } | ||
| */ | ||
| fn.describe = function (text, display) { | ||
| fn.describe = function (text, langOrDisplay, display) { | ||
| // p5._validateParameters('describe', arguments); | ||
| if (typeof text !== 'string') { | ||
| return; | ||
| } | ||
| const parsedOptions = _parseOptions(this, langOrDisplay, display); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To avoid duplication of this code, you can use decorators. For example, here how we use decorators for v ector validation, where very similar code runs for each of the vector binary operations: https://github.com/processing/p5.js/blob/main/src/math/patch-vector.js we wanted to avoid copy-pasting the same code in 5 places, and we used that decorator pattern instead. Firendly Errors also use decorators. Please do try using this pattern to avoid this option interpretation (even though in your case its just 2 places); the decorators API was recently added so don't hesitate to @ me on discord for help with this one. |
||
| display = parsedOptions.display; | ||
| const { lang } = parsedOptions; | ||
| const cnvId = this.canvas.id; | ||
| //calls function that adds punctuation for better screen reading | ||
| text = _descriptionText(text); | ||
| //if there is no dummyDOM | ||
| if (!this.dummyDOM) { | ||
| this.dummyDOM = document.getElementById(cnvId).parentNode; | ||
| } | ||
| //check if html structure for description is ready | ||
| if (!this.descriptions) { | ||
| this.descriptions = {}; | ||
| } | ||
|
|
@@ -150,6 +167,7 @@ function describe(p5, fn) { | |
| this._describeHTML('label', text); | ||
| } | ||
| } | ||
| _setDescriptionLang(this, lang); | ||
| }; | ||
|
|
||
| /** | ||
|
|
@@ -161,15 +179,22 @@ function describe(p5, fn) { | |
| * The first parameter, `name`, is the name of the element. | ||
| * | ||
| * The second parameter, `text`, is the description of the element. | ||
| * | ||
| * The third parameter, `display`, is optional. It determines how the | ||
| * description is displayed. If `LABEL` is passed, as in | ||
| * `describe('A description.', LABEL)`, the description will be visible in | ||
| * a div element next to the canvas. Using `LABEL` creates unhelpful | ||
| * duplicates for screen readers. Only use `LABEL` during development. If | ||
| * `FALLBACK` is passed, as in `describe('A description.', FALLBACK)`, the | ||
| * description will only be visible to screen readers. This is the default | ||
| * mode. | ||
| * | ||
| * The third parameter, `langOrDisplay`, is optional. It can either | ||
| * determine the language of the description or how the description | ||
| * is displayed. The description can be displayed with either `LABEL` | ||
| * or `FALLBACK` | ||
| * | ||
| * The fourth parameter, `display`, is optional but is only used if the third | ||
| * parameter is lang, as in the language of the description, and the user wants | ||
| * to determine how the description is displayed as well. In this case, they can | ||
| * pass either `LABEL` or `FALLBACK` as the fourth parameter. | ||
| * | ||
| * If `LABEL` is passed, as in `describe('A description.', LABEL)`, | ||
| * the description will be visible in a div element next to the canvas. Using | ||
| * `LABEL` creates unhelpful duplicates for screen readers. Only use `LABEL` | ||
| * during development. If `FALLBACK` is passed, as in `describe('A description.', FALLBACK)`, | ||
| * the description will only be visible to screen readers. This is the default mode. | ||
| * | ||
| * Read | ||
| * <a href="/learn/accessible-labels.html">Writing accessible canvas descriptions</a> | ||
|
|
@@ -178,8 +203,8 @@ function describe(p5, fn) { | |
| * @method describeElement | ||
| * @param {String} name name of the element. | ||
| * @param {String} text description of the element. | ||
| * @param {(FALLBACK|LABEL)} [display] either LABEL or FALLBACK. | ||
| * | ||
| * @param {(FALLBACK|LABEL|String)} [langOrDisplay] valid lang attribute or either LABEL or FALLBACK. | ||
| * @param {(FALLBACK|LABEL)} [display] either LABEL or FALLBACK. | ||
| * @example | ||
| * function setup() { | ||
| * background('pink'); | ||
|
|
@@ -229,11 +254,14 @@ function describe(p5, fn) { | |
| * } | ||
| */ | ||
|
|
||
| fn.describeElement = function (name, text, display) { | ||
| fn.describeElement = function (name, text, langOrDisplay, display) { | ||
| // p5._validateParameters('describeElement', arguments); | ||
| if (typeof text !== 'string' || typeof name !== 'string') { | ||
| return; | ||
| } | ||
| const parsedOptions = _parseOptions(this, langOrDisplay, display); | ||
| display = parsedOptions.display; | ||
| const { lang } = parsedOptions; | ||
| const cnvId = this.canvas.id; | ||
| //calls function that adds punctuation for better screen reading | ||
| text = _descriptionText(text); | ||
|
|
@@ -242,8 +270,11 @@ function describe(p5, fn) { | |
| //remove any special characters from name to use it as html id | ||
| name = name.replace(/[^a-zA-Z0-9]/g, ''); | ||
|
|
||
| // Inject lang attribute | ||
| let langAttr = typeof lang === 'string' ? ` lang="${lang}"` : ''; | ||
|
|
||
| //store element description | ||
| let inner = `<th scope="row">${elementName}</th><td>${text}</td>`; | ||
| let inner = `<th scope="row"${langAttr}>${elementName}</th><td${langAttr}>${text}</td>`; | ||
| //if there is no dummyDOM | ||
| if (!this.dummyDOM) { | ||
| this.dummyDOM = document.getElementById(cnvId).parentNode; | ||
|
|
@@ -289,6 +320,69 @@ function describe(p5, fn) { | |
| * | ||
| */ | ||
|
|
||
| // Helps parse the optional parameters regardless of order | ||
| function _parseOptions(pInst, langOrDisplay, display) { | ||
| // Check if 2nd parameter is an object | ||
| if (typeof langOrDisplay === 'object' && langOrDisplay !== null) { | ||
| let finalDisplay = display; | ||
| if (finalDisplay === undefined) { | ||
| finalDisplay = langOrDisplay.display; | ||
| } | ||
| return { | ||
| display: finalDisplay, | ||
| lang: langOrDisplay.lang | ||
| }; | ||
| } | ||
|
|
||
| // Check if langOrDisplay is display (LABEL or FALLBACK) | ||
| // Example: describe describe('text', LABEL) | ||
| // If 3 parameters: describe('text', LABEL, 'es') | ||
| if (langOrDisplay === pInst.LABEL || langOrDisplay === pInst.FALLBACK) { | ||
| let finalLang = undefined; | ||
| // Check if 3rd parameter exists and is a string (lang attribute) | ||
| if (typeof display === 'string') { | ||
| finalLang = display; | ||
| } | ||
| else if (typeof display === 'object' && display !== null) { | ||
| finalLang = display.lang; | ||
| } | ||
| return {display: langOrDisplay, lang: finalLang}; | ||
| } | ||
|
|
||
| // langOrDisplay is lang | ||
| // Example: describe('text', 'es') | ||
| // If 3 parameters: describe('text', 'es', LABEL) | ||
| return {display: display, lang: langOrDisplay}; | ||
| } | ||
|
|
||
| function _setDescriptionLang(pInst, lang) { | ||
| const canvas = pInst.canvas.elt || pInst.elt || pInst.canvas; | ||
| if (typeof lang === 'string') { | ||
| canvas.setAttribute('lang', lang); | ||
| } | ||
| else { | ||
| canvas.removeAttribute('lang'); | ||
| } | ||
|
|
||
| if (pInst.descriptions.fallback) { | ||
| if (typeof lang === 'string') { | ||
| pInst.descriptions.fallback.setAttribute('lang', lang); | ||
| } | ||
| else { | ||
| pInst.descriptions.fallback.removeAttribute('lang'); | ||
| } | ||
| } | ||
|
|
||
| if (pInst.descriptions.label) { | ||
| if (typeof lang === 'string') { | ||
| pInst.descriptions.label.setAttribute('lang', lang); | ||
| } | ||
| else { | ||
| pInst.descriptions.label.removeAttribute('lang'); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // check that text is not LABEL or FALLBACK and ensure text ends with punctuation mark | ||
| function _descriptionText(text) { | ||
| if (text === 'label' || text === 'fallback') { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
|
|
||
| <head> | ||
| <meta charset="utf-8"> | ||
| <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||
| <title>Accessibility: describe language</title> | ||
| <link rel="stylesheet" href="../../styles.css"> | ||
|
|
||
| <script language="javascript" src="../../../../lib/p5.js"></script> | ||
| <script language="javascript" type="text/javascript" src="sketch.js"></script> | ||
| </head> | ||
|
|
||
| <body> | ||
| <header> | ||
| <p>Accessibility: describe() language test</p> | ||
| </header> | ||
|
|
||
| <main> | ||
| <p>Read each region with a screen reader. Compare regions without a lang attribute with those that have one.</p> | ||
|
|
||
| <div aria-label="English No Lang"> | ||
| <p>this is a test</p> | ||
| </div> | ||
| <div aria-label="English With Lang" lang="en"> | ||
| <p>this is a test</p> | ||
| </div> | ||
| <div aria-label="Spanish No Lang"> | ||
| <p>esto es una prueba</p> | ||
| </div> | ||
| <div aria-label="Spanish With Lang" lang="es"> | ||
| <p>esto es una prueba</p> | ||
| </div> | ||
| <div aria-label="Vietnamese No Lang"> | ||
| <p>Cái này là bài thi</p> | ||
| </div> | ||
| <div aria-label="Vietnamese With Lang" lang="vi"> | ||
| <p>Cái này là bài thi</p> | ||
| </div> | ||
|
|
||
| <h1>p5.js generated descriptions</h1> | ||
| <p>Read the descriptions generated by the canvases below. The visible LABEL descriptions are included for inspection.</p> | ||
| </main> | ||
| </body> | ||
|
|
||
| </html> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| function setup() { | ||
| // Uncomment any of the following lines to test | ||
|
|
||
| createCanvas(400, 400); | ||
|
|
||
| // Text only (fallback is the default) | ||
| // describe('Cái này là bài thi'); | ||
|
|
||
| // Text and display | ||
| // describe('Esto es una prueba', LABEL); | ||
|
|
||
| // Text and lang | ||
| //describe('Esto es una prueba', 'es'); | ||
|
|
||
| // Text, lang, and display | ||
| // describe('Cái này là bài thi', 'vi', LABEL); | ||
|
|
||
| // Text, display, and lang | ||
| // describe('यह टेस्ट है', LABEL, 'hi'); | ||
|
|
||
|
|
||
|
|
||
| // Name and text only (fallback is the default) | ||
| // describeElement('a', 'Cái này là bài thi'); | ||
|
|
||
| // Name, text, and display | ||
| // describeElement('b', 'Esto es una prueba', LABEL); | ||
|
|
||
| // Name, text, and lang | ||
| // describeElement('c', 'Esto es una prueba', 'es'); | ||
|
|
||
| // Name, text, lang, and display | ||
| // describeElement('d', 'Cái này là bài thi', 'vi', LABEL); | ||
|
|
||
| // Name, text, display, and lang | ||
| // describeElement('e', 'यह टेस्ट है', LABEL, 'hi'); | ||
|
|
||
| fill('blue'); | ||
| circle(200, 150, 100); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have this pattern anywhere else? I'm not remembering anything off the top of my head. But it's quite a challenging pattern. cc @perminder-17 sorry if it was already discussed somewhere and I missed it, but my thoughts below.
From your examples:
So the second one is lang or display also? Maybe it should be
parameter1andparameter2and either can be lang or display but to be honest if we dont already have precedent for this kind of approach, I am a little worried about it being very confusing for learners, and resulting in outdated documentation for contributors.