Repository navigation
Expand file tree
/
Copy pathfont-select.js
More file actions
110 lines (96 loc) · 5.5 KB
/
Copy pathfont-select.js
File metadata and controls
110 lines (96 loc) · 5.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
'use strict';
/**
* font-select.js — список выбора шрифта стиля «Цифры»: div.font-select
* притворяется форм-контролем с .value.
*
* Вынесено из inline-скрипта electron-control.html сразу при добавлении:
* `tests/control-decomposition.test.js` держит панель под жёстким лимитом строк
* именно затем, чтобы новая логика не оседала внутри HTML по умолчанию — а не
* только затем, чтобы однажды вынести уже накопившееся. Функция не знает о
* timerController — только об элементе, который ей передали, и о реестре
* шрифтов `DigitsStyle`, поэтому переиспользуется тремя списками
* (виджет/часы/дисплей) без единой строчки, завязанной на контроллер.
*
* КРИТИЧНО, тот же инвариант, что у _attachSegmented (тот пока остаётся
* в панели рядом со своим контроллером): присваивание .value НЕ порождает
* 'change'. Событие шлёт ТОЛЬКО клик пользователя. Нарушение этого правила у
* сегментированного контрола убило синхронизацию стиля часов целиком —
* восстановление значения при загрузке панели уничтожало сохранённую
* настройку.
*
* Нативный <select> не годится: на macOS попап рисует система, и font-family
* на <option> не применяется — превью, ради которого контрол и нужен, просто
* не работало бы.
*/
import * as DigitsStyle from './digits-style.js';
function attachFontSelect(el) {
if (!el || !el.classList || !el.classList.contains('font-select')) { return; }
const fonts = DigitsStyle.DIGIT_FONTS;
// id пункта содержит id КОНТЕЙНЕРА — на панели три независимых списка
// (виджет/часы/дисплей) в одном document, и голое "option-bebas" столкнулось
// бы у всех трёх.
const optionId = (fontId) => `${el.id}-option-${fontId}`;
el.innerHTML = '';
for (const font of fonts) {
const option = document.createElement('div');
option.className = 'font-option';
option.setAttribute('role', 'option');
option.id = optionId(font.id);
option.dataset.val = font.id;
option.tabIndex = -1;
const name = document.createElement('span');
name.className = 'font-option-name';
name.textContent = font.label;
const sample = document.createElement('span');
sample.className = 'font-option-sample';
sample.textContent = '12:34';
sample.style.fontFamily = font.family;
sample.style.fontWeight = String(font.weight);
option.append(name, sample);
el.appendChild(option);
}
const apply = (v) => {
const font = DigitsStyle.resolveFont(v);
el.dataset.value = font.id;
el.querySelectorAll('.font-option').forEach((o) => {
const on = o.dataset.val === font.id;
o.classList.toggle('active', on);
o.setAttribute('aria-selected', on ? 'true' : 'false');
});
// Фокус остаётся НА КОНТЕЙНЕРЕ (tabindex=0 на .font-select ниже,
// у пунктов tabindex=-1) — паттерн listbox с "виртуальным" фокусом.
// Без aria-activedescendant скринридер не знает, какой пункт сейчас
// выделен, хотя визуально и по клику/стрелкам всё работает: клик и
// ArrowDown реально меняют .value, но ни один из них НЕ переносит
// DOM-фокус на сам пункт, поэтому единственный канал для ассистивных
// технологий — этот атрибут, и без него он не выставлялся вовсе.
el.setAttribute('aria-activedescendant', optionId(font.id));
};
if (!Object.getOwnPropertyDescriptor(el, 'value')) {
Object.defineProperty(el, 'value', {
get() { return this.dataset.value || DigitsStyle.DEFAULT_FONT_ID; },
set(v) { apply(v); },
configurable: true
});
}
el.querySelectorAll('.font-option').forEach((option) => {
option.addEventListener('click', () => {
apply(option.dataset.val);
el.dispatchEvent(new Event('change', { bubbles: true }));
});
});
el.setAttribute('tabindex', '0');
el.addEventListener('keydown', (e) => {
if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') { return; }
e.preventDefault();
const ids = fonts.map((f) => f.id);
const at = ids.indexOf(el.value);
const next = e.key === 'ArrowDown'
? (at + 1) % ids.length
: (at - 1 + ids.length) % ids.length;
apply(ids[next]);
el.dispatchEvent(new Event('change', { bubbles: true }));
});
apply(el.dataset.value);
}
export { attachFontSelect };