Skip to content

Commit 0213945

Browse files
✨ Add label attribute for accessible name
1 parent 1f7748f commit 0213945

File tree

2 files changed

+112
-50
lines changed

2 files changed

+112
-50
lines changed

src/class/HTMLCodeBlockElement.ts

Lines changed: 106 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,30 @@ export default class HTMLCodeBlockElement extends HTMLElement {
3939
return endgine.highlightAuto(src);
4040
}
4141

42-
#shadowRoot: ShadowRoot;
42+
#slots = (() => {
43+
/**
44+
* @param name - The value of name attribute for the slot element
45+
* @returns - The slot element
46+
*/
47+
const mkslot = (name: string, id?: string) => {
48+
const slot = document.createElement('slot');
49+
50+
slot.name = name;
51+
52+
if (id) {
53+
slot.id = id;
54+
}
55+
56+
return slot;
57+
};
58+
59+
return {
60+
name: mkslot('name', 'name'),
61+
copyButton: mkslot('copy-button'),
62+
code: mkslot('code'),
63+
};
64+
})();
65+
#a11yName: HTMLElement;
4366
#codeBlock: HTMLElement;
4467
#codeWrap: HTMLPreElement;
4568
/** Actual value of the accessor `value` */
@@ -50,22 +73,24 @@ export default class HTMLCodeBlockElement extends HTMLElement {
5073
#language: string = '';
5174
/** Actual value of the accessor `controls` */
5275
#controls: boolean = false;
53-
5476
/** Outputs the resulting syntax-highlighted markup to the DOM. */
5577
#render = function (this: HTMLCodeBlockElement) {
5678
if (!this.parentNode) {
5779
return;
5880
}
5981

6082
/** The resulting syntax-highlighted markup */
61-
const markup = HTMLCodeBlockElement.highlight(this.#value, {
83+
const {value: markup} = HTMLCodeBlockElement.highlight(this.#value, {
6284
language: this.#language,
63-
}).value;
85+
});
6486

6587
// initialize
6688
this.textContent = '';
89+
this.#a11yName.textContent = this.#label;
90+
this.#slots.name.hidden = !this.#label;
6791
this.#codeBlock.textContent = '';
6892
this.#codeBlock.insertAdjacentHTML('afterbegin', markup);
93+
this.append(this.#a11yName);
6994
this.append(this.#codeWrap);
7095
}
7196

@@ -87,14 +112,13 @@ export default class HTMLCodeBlockElement extends HTMLElement {
87112
return this.#label;
88113
}
89114

90-
set label(name: string) {
91-
// TODO: Accessiblity Treeにアクセシブルネームを提供する
92-
this.#label = name || '';
93-
94-
if (this.#label) {
95-
this.setAttribute('label', name);
96-
} else {
115+
set label(value: string) {
116+
if (value === null) {
117+
this.#label = '';
97118
this.removeAttribute('label');
119+
} else {
120+
this.#label = String(value);
121+
this.setAttribute('label', this.#label);
98122
}
99123

100124
this.#render();
@@ -108,13 +132,13 @@ export default class HTMLCodeBlockElement extends HTMLElement {
108132
return this.#language;
109133
}
110134

111-
set language(name: string) {
112-
this.#language = name || '';
113-
114-
if (this.#language) {
115-
this.setAttribute('language', name);
116-
} else {
135+
set language(value: any) {
136+
if (value === null) {
137+
this.#language = '';
117138
this.removeAttribute('language');
139+
} else {
140+
this.#language = String(value);
141+
this.setAttribute('language', this.#language);
118142
}
119143

120144
this.#render();
@@ -128,9 +152,9 @@ export default class HTMLCodeBlockElement extends HTMLElement {
128152
return this.#controls;
129153
}
130154

131-
set controls(flag: boolean) {
132-
// TODO: コピーボタン、ラベルの表示切り替え
133-
this.#controls = flag;
155+
set controls(value: boolean) {
156+
// TODO: コピーボタンの表示切り替え
157+
this.#controls = value;
134158

135159
if (this.#controls) {
136160
this.setAttribute('controls', '');
@@ -164,7 +188,7 @@ export default class HTMLCodeBlockElement extends HTMLElement {
164188
// string
165189
case 'label':
166190
case 'language':
167-
this[attrName] = newValue || '';
191+
this[attrName] = newValue;
168192

169193
break;
170194

@@ -181,41 +205,74 @@ export default class HTMLCodeBlockElement extends HTMLElement {
181205
constructor() {
182206
super();
183207

208+
/* -------------------------------------------------------------------------
209+
* Setup Shadow DOM contents
210+
* ---------------------------------------------------------------------- */
184211
/**
185-
* @param name - The value of name attribute for the slot element
186-
* @returns - The slot element
212+
* The container of minimum text that will be read even
213+
* if the accessible name (label attribute value) is omitted.
187214
*/
188-
const mkslot = (name: string) => {
189-
const slot = document.createElement('slot');
190-
191-
slot.name = name;
215+
const a11yNamePrefix = (() => {
216+
const span = document.createElement('span');
217+
218+
span.id = 'semantics';
219+
span.hidden = true;
220+
span.textContent = 'Code Block';
221+
222+
return span;
223+
})()
224+
/** Container of accessible names (label attribute values). */
225+
const a11yName = (() => {
226+
const span = document.createElement('span');
227+
228+
span.slot = 'name';
229+
span.textContent = this.getAttribute('label') || '';
230+
231+
return span;
232+
})();
233+
const codeElm = (() => {
234+
const code = document.createElement('code');
235+
236+
code.tabIndex = 0;
237+
code.className = 'hljs'; // TODO: Make it variable
238+
239+
return code;
240+
})();
241+
const preElm = (() => {
242+
const pre = document.createElement('pre');
243+
244+
pre.slot = 'code';
245+
pre.append(codeElm);
246+
247+
return pre;
248+
})();
249+
const container = (() => {
250+
const div = document.createElement('div');
251+
252+
div.append(...Object.values(this.#slots));
253+
div.setAttribute('role', 'group');
254+
div.setAttribute('aria-labelledby', 'semantics name');
255+
256+
return div;
257+
})();
258+
const shadowRoot = this.attachShadow({
259+
mode: 'closed',
260+
});
192261

193-
return slot;
194-
}
195-
const slots = [
196-
mkslot('label'),
197-
mkslot('copy-button'),
198-
mkslot('code'),
199-
];
200-
const pre = document.createElement('pre');
201-
const code = document.createElement('code');
262+
shadowRoot.append(a11yNamePrefix);
263+
shadowRoot.append(container);
202264

203-
code.tabIndex = 0;
204-
code.className = 'hljs'; // TODO: Make it variable
205-
pre.slot = 'code';
206-
pre.append(code);
207265

208-
// Hard private props initialize
209-
this.#value = (this.textContent || '').replace(/^\n/, '');
210-
this.#label = this.getAttribute('label') || '';
266+
/* -------------------------------------------------------------------------
267+
* Hard private props initialize
268+
* ---------------------------------------------------------------------- */
269+
this.#value = (this.textContent || '').replace(/^\n/, '').replace(/\n$/, '');
270+
this.#label = a11yName.textContent || '';
211271
this.#language = this.getAttribute('language') || '';
212272
this.#controls = this.getAttribute('controls') !== null;
213-
this.#shadowRoot = this.attachShadow({
214-
mode: 'closed',
215-
});
216-
this.#codeBlock = code;
217-
this.#codeWrap = pre;
218-
this.#shadowRoot.append(...slots);
273+
this.#a11yName = a11yName;
274+
this.#codeBlock = codeElm;
275+
this.#codeWrap = preElm;
219276
}
220277
}
221278

src/utils/add-style.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@ style.textContent = `
1616
top: 0;
1717
left: 0;
1818
padding: 0 5px;
19+
max-width: 90%;
1920
color: #fff;
21+
white-space: pre;
2022
line-height: 1.5;
23+
overflow: hidden;
24+
text-overflow: ellipsis;
2125
background: #75758a;
26+
box-sizing: border-box;
2227
}
2328
code-block pre,
2429
code-block code {
@@ -33,7 +38,7 @@ style.textContent = `
3338
font-size: 100%;
3439
overflow-x: auto;
3540
}
36-
code-block[label] pre code {
41+
code-block[label]:not([label=""]) pre code {
3742
padding-top: 2em;
3843
}
3944
`;

0 commit comments

Comments
 (0)