#LitElement を触っていたら、プロパティと属性の変換をカスタマイズできることを知った(Properties - LitElement)。これを使えば、JavaScriptのプロパティとしてブール型を使いながら、DOM要素としてはWAI ARIAの状態属性にするのが簡単になるのではないかと思って、試してみた。
お題として、トグルボタンを作ることにした。HTMLではこんな感じ。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<style>
body {
padding-top: 6em;
background: #fafafa;
text-align: center;
}
</style>
<script type="module" src="../toggle-button.js"></script>
</head>
<body>
<toggle-button role=button tabindex=0>No aria-pressed at first</toggle-button>
<toggle-button role=button tabindex=0 aria-pressed=false>Not pressed at first</toggle-button>
<toggle-button role=button tabindex=0 aria-pressed=true>Pressed at first</toggle-button>
</body>
</html>
雰囲気で実装
attribute
に文字列でHTML属性名を渡すと、その属性名に変換してくれる。
ここでは、JavaScriptでtoggleButton.pressed = true
とやるのとHTMLで<toggle-button aria-pressed="true"></toggle-button>
とやるのとが同期されるようにする。
import { html, css, LitElement } from 'lit-element';
export class ToggleButton extends LitElement {
static get styles() {
return css`
:host {
padding: 0.5em 1em;
background-color: #e8e8e8;
}
:host([aria-pressed="true"]) {
background-color: #cccccc;
}
`;
}
static get properties() {
return {
pressed: { // JavaScriptでのプロパティ名
attribute: 'aria-pressed', // HTMLでの属性名
reflect: true // JavaScriptでのプロパティの変更を、HTML属性に反映させるかどうか
}
};
}
render() {
return html`
<span @click=${this.toggle}><slot></slot></span>
`;
}
toggle(event) {
this.pressed = ! this.pressed;
}
}
大体よい。ただ、「Not pressed at first」ボタンの、最初の一回のクリックだけ、うまくいかない、aria-pressed
が"true"
にならない。二回押すと"true"
になる。それ以降はちゃんと、押す度に"true"
と"false"
が切り替わってくれる。
プロパティ値 <-> 属性値間のカスタムコンバーター
そこをちゃんとするためにコンバーターを定義してやったらうまくいく。
import { html, css, LitElement } from 'lit-element';
export class ToggleButton extends LitElement {
// (snip)
static get properties() {
return {
pressed: {
attribute: 'aria-pressed',
reflect: true,
converter: {
fromAttribute: (value) => {
return value === 'true';
},
toAttribute: (value) => {
return value ? 'true' : 'false';
}
}
}
};
// (snip)
}
素直に実装したい
ただ、最初の一回のために、こんなにたくさん書かないといけないのは、何となく不満だ。どうにかできないものか。問題は、初めてクリックする前、つまり初期化時に、HTMLに書いた属性を読み取ってくれないことだろうと思う。
初期化の時にプロパティがどうなっているか調べてみよう。converter
を再び削除して、connectedCallback()
でログを出してみる。
import { html, css, LitElement } from 'lit-element';
export class ToggleButton extends LitElement {
// (snip)
static get properties() {
return {
pressed: {
attribute: 'aria-pressed',
reflect: true
}
};
}
// (snip)
connectedCallback() {
super.connectedCallback();
console.log(this.textContent);
console.log(this.pressed, typeof this.pressed); // => false string
console.log(this.getAttribute('aria-pressed'), typeof this.getAttribute('aria-pressed')); // false string
}
// (snip)
}
JavaScriptの方のプロパティ(this.pressed
)も文字列になってる……。空じゃない文字列"false"
はJavaScriptではtruthyだから、なるほど
- 最初は
aria-pressed="false"
、this.pressed == true
- ここでクリックすると
this.pressed = false
になり、aria-pressed="false"
として反映される - クリック前も後も
aria-pressed="false"
に対してのCSSセレクターが使われ、変化がない。
ということか。
だったら初期状態でaria-pressed="false"
なのをthis.pressed == false
として解釈してやればいいんだな。
import { html, css, LitElement } from 'lit-element';
export class ToggleButton extends LitElement {
// (snip)
connectedCallback() {
super.connectedCallback();
this.pressed = this.getAttribute('aria-pressed') === 'true';
}
// (snip)
}
これでうまくいきました。
aria-pressedがない場合の扱い
ところで、aria-pressed
が存在しない場合、そもそもデフォルトはどうなっているんだろう。定義を見てみる。
undefined (default) The element does not support being pressed.
そもそも「押す」という機能をサポートしない、つまりトグルボタンにならない……。これは、ユーザーがマークアップ時にaria-pressed
を書かなかった場合に"true"
にするか"false"
にするか、このカスタムエレメントの場合を決めて、初期化処理を入れる必要があるということだな。今回は(そして多くの場合そうだろうが)、何もない場合はaria-pressed="false"
として扱うことにする。
import { html, css, LitElement } from 'lit-element';
export class ToggleButton extends LitElement {
// (snip)
connectedCallback() {
super.connectedCallback();
if (! this.hasAttribute('aria-pressed')) {
this.pressed = false;
} else {
this.pressed = this.getAttribute('aria-pressed') === 'true';
}
}
// (snip)
}
これで、「No aria-pressed at first」ボタンも、自動的にaria-pressed="false"
が付与されるようになった。
reflected-aria-attributes
これやっててふと思い出したんだけど、昔こうした機能を(ウェブコンポーネントじゃなくて)HTML要素に付与するNPMパッケージを作っていたなあ:reflected-aria-attributes
ソースコード
今日の試行錯誤の履歴(ソースコード)はこちら:KitaitiMakoto/lit-element-and-wai-aria-states
Comments
No comments yet. Be the first to react!