@lilydesignsystem/nunjucks-text-size-picker 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +35 -128
- package/dist/index.js +15 -97
- package/package.json +4 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createListboxKeyboard } from '@lilydesignsystem/nunjucks-listbox-behavior';
|
|
2
|
+
|
|
1
3
|
// TextSizePicker client-side runtime.
|
|
2
4
|
//
|
|
3
5
|
// Pairs with text-size-picker.njk. The macro renders the markup with
|
|
@@ -5,9 +7,11 @@
|
|
|
5
7
|
// the browser and owns two things:
|
|
6
8
|
//
|
|
7
9
|
// A. The listbox INTERACTION (new in the icon-button release): open /
|
|
8
|
-
// close
|
|
9
|
-
//
|
|
10
|
-
//
|
|
10
|
+
// close and focus movement here; the APG listbox keyboard contract
|
|
11
|
+
// itself is delegated to the shared
|
|
12
|
+
// @lilydesignsystem/nunjucks-listbox-behavior module. None of this
|
|
13
|
+
// exists in the server markup — the button is inert until this
|
|
14
|
+
// module runs. See docs/ssr.md.
|
|
11
15
|
//
|
|
12
16
|
// B. The text-size LIFECYCLE (unchanged):
|
|
13
17
|
// 0. Read the consumer's `value` prop from
|
|
@@ -31,8 +35,6 @@
|
|
|
31
35
|
//
|
|
32
36
|
// See spec/index.md §4.3 (client.js exports), §5 (behaviour).
|
|
33
37
|
|
|
34
|
-
/** How long the typeahead buffer survives between keystrokes, in ms. */
|
|
35
|
-
const TYPEAHEAD_RESET_MS = 500;
|
|
36
38
|
|
|
37
39
|
/**
|
|
38
40
|
* Resolve a size slug to its display label: each hyphen-separated word
|
|
@@ -68,13 +70,6 @@ function safeStorageSet(key, value) {
|
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
/** jsdom and older browsers do not always implement scrollIntoView. */
|
|
72
|
-
function scrollIntoViewIfPossible(el) {
|
|
73
|
-
if (el && typeof el.scrollIntoView === "function") {
|
|
74
|
-
el.scrollIntoView({ block: "nearest" });
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
73
|
/**
|
|
79
74
|
* Wire one rendered TextSizePicker root.
|
|
80
75
|
*
|
|
@@ -93,7 +88,6 @@ function initTextSizePicker(root, opts = {}) {
|
|
|
93
88
|
|
|
94
89
|
const options = Array.from(list.querySelectorAll('[role="option"]'));
|
|
95
90
|
const values = options.map((o) => o.getAttribute("data-value") || "");
|
|
96
|
-
const labels = options.map((o) => (o.textContent || "").trim());
|
|
97
91
|
|
|
98
92
|
const storageKey =
|
|
99
93
|
root.getAttribute("data-lily-text-size-picker-storage-key") || "";
|
|
@@ -106,9 +100,6 @@ function initTextSizePicker(root, opts = {}) {
|
|
|
106
100
|
|
|
107
101
|
let current = "";
|
|
108
102
|
let open = false;
|
|
109
|
-
let activeIndex = -1;
|
|
110
|
-
let typeahead = "";
|
|
111
|
-
let typeaheadTimer;
|
|
112
103
|
|
|
113
104
|
// -----------------------------------------------------------------
|
|
114
105
|
// Applying a size
|
|
@@ -140,22 +131,13 @@ function initTextSizePicker(root, opts = {}) {
|
|
|
140
131
|
|
|
141
132
|
// -----------------------------------------------------------------
|
|
142
133
|
// Open / close / active-option movement
|
|
134
|
+
//
|
|
135
|
+
// Arrow move/clamp, Home/End, typeahead, PageUp/PageDown, and
|
|
136
|
+
// Enter/Space/Escape/Tab are owned by the shared listbox-behavior
|
|
137
|
+
// controller below (`keyboard`); this picker only decides what
|
|
138
|
+
// open/close/choose mean.
|
|
143
139
|
// -----------------------------------------------------------------
|
|
144
140
|
|
|
145
|
-
function setActive(index) {
|
|
146
|
-
activeIndex = index;
|
|
147
|
-
options.forEach((o, i) => {
|
|
148
|
-
if (i === index) o.setAttribute("data-active", "");
|
|
149
|
-
else o.removeAttribute("data-active");
|
|
150
|
-
});
|
|
151
|
-
if (index >= 0 && options[index]) {
|
|
152
|
-
list.setAttribute("aria-activedescendant", options[index].id);
|
|
153
|
-
scrollIntoViewIfPossible(options[index]);
|
|
154
|
-
} else {
|
|
155
|
-
list.removeAttribute("aria-activedescendant");
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
141
|
function openList(startIndex) {
|
|
160
142
|
const selected = values.indexOf(current);
|
|
161
143
|
// An empty list has no option to activate; -1 keeps
|
|
@@ -172,7 +154,7 @@ function initTextSizePicker(root, opts = {}) {
|
|
|
172
154
|
open = true;
|
|
173
155
|
list.hidden = false;
|
|
174
156
|
button.setAttribute("aria-expanded", "true");
|
|
175
|
-
setActive(start);
|
|
157
|
+
keyboard.setActive(start);
|
|
176
158
|
// Focus moves to the listbox; the active option is conveyed via
|
|
177
159
|
// aria-activedescendant, per the APG listbox pattern.
|
|
178
160
|
list.focus({ preventScroll: true });
|
|
@@ -183,7 +165,7 @@ function initTextSizePicker(root, opts = {}) {
|
|
|
183
165
|
open = false;
|
|
184
166
|
list.hidden = true;
|
|
185
167
|
button.setAttribute("aria-expanded", "false");
|
|
186
|
-
setActive(-1);
|
|
168
|
+
keyboard.setActive(-1);
|
|
187
169
|
if (refocus) button.focus({ preventScroll: true });
|
|
188
170
|
}
|
|
189
171
|
|
|
@@ -193,39 +175,26 @@ function initTextSizePicker(root, opts = {}) {
|
|
|
193
175
|
closeList();
|
|
194
176
|
}
|
|
195
177
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const query = sameCharRun ? lower : typeahead;
|
|
217
|
-
const anchor = activeIndex < 0 ? 0 : activeIndex;
|
|
218
|
-
const start = sameCharRun ? anchor + 1 : anchor;
|
|
219
|
-
// Search forward, wrapping once — typeahead wraps even though the
|
|
220
|
-
// arrows clamp, or options above the cursor would be untypable.
|
|
221
|
-
for (let n = 0; n < options.length; n++) {
|
|
222
|
-
const i = (start + n) % options.length;
|
|
223
|
-
if (labels[i].toLowerCase().startsWith(query)) {
|
|
224
|
-
setActive(i);
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
178
|
+
const keyboard = createListboxKeyboard(list, {
|
|
179
|
+
clamp: true,
|
|
180
|
+
typeahead: true,
|
|
181
|
+
pageSize: 10,
|
|
182
|
+
onActivate: choose,
|
|
183
|
+
onEscape: () => closeList(),
|
|
184
|
+
onTabOut: () => {
|
|
185
|
+
// Tab moves on — but focus goes to the button FIRST, without
|
|
186
|
+
// cancelling the key (the shared controller never prevents Tab's
|
|
187
|
+
// default). Hiding the focused list drops focus to <body>, and
|
|
188
|
+
// the browser then computes the default Tab move from the top of
|
|
189
|
+
// the document, so tabbing out of an open picker teleported the
|
|
190
|
+
// user to the page's first tab stop. From the button, the default
|
|
191
|
+
// Tab lands exactly where leaving the picker should. Guard the
|
|
192
|
+
// METHOD, not just the element: this shape has bitten these
|
|
193
|
+
// helpers before.
|
|
194
|
+
button?.focus?.({ preventScroll: true });
|
|
195
|
+
closeList(false);
|
|
196
|
+
},
|
|
197
|
+
});
|
|
229
198
|
|
|
230
199
|
// -----------------------------------------------------------------
|
|
231
200
|
// Event handlers
|
|
@@ -251,66 +220,6 @@ function initTextSizePicker(root, opts = {}) {
|
|
|
251
220
|
}
|
|
252
221
|
}
|
|
253
222
|
|
|
254
|
-
function onListKeydown(event) {
|
|
255
|
-
switch (event.key) {
|
|
256
|
-
case "ArrowDown":
|
|
257
|
-
event.preventDefault();
|
|
258
|
-
moveActive(1);
|
|
259
|
-
break;
|
|
260
|
-
case "ArrowUp":
|
|
261
|
-
event.preventDefault();
|
|
262
|
-
moveActive(-1);
|
|
263
|
-
break;
|
|
264
|
-
case "Home":
|
|
265
|
-
event.preventDefault();
|
|
266
|
-
setActive(0);
|
|
267
|
-
break;
|
|
268
|
-
case "End":
|
|
269
|
-
event.preventDefault();
|
|
270
|
-
setActive(options.length - 1);
|
|
271
|
-
break;
|
|
272
|
-
case "Enter":
|
|
273
|
-
case " ":
|
|
274
|
-
event.preventDefault();
|
|
275
|
-
if (activeIndex >= 0) choose(activeIndex);
|
|
276
|
-
break;
|
|
277
|
-
case "Escape":
|
|
278
|
-
event.preventDefault();
|
|
279
|
-
closeList();
|
|
280
|
-
break;
|
|
281
|
-
case "PageUp":
|
|
282
|
-
event.preventDefault();
|
|
283
|
-
moveActive(-10);
|
|
284
|
-
break;
|
|
285
|
-
case "PageDown":
|
|
286
|
-
// ±10, clamped: an APG-optional key for long lists.
|
|
287
|
-
event.preventDefault();
|
|
288
|
-
moveActive(10);
|
|
289
|
-
break;
|
|
290
|
-
case "Tab":
|
|
291
|
-
// Tab moves on — but focus goes to the button FIRST, without
|
|
292
|
-
// cancelling the key. Hiding the focused list drops focus to
|
|
293
|
-
// <body>, and the browser then computes the default Tab move
|
|
294
|
-
// from the top of the document, so tabbing out of an open
|
|
295
|
-
// picker teleported the user to the page's first tab stop.
|
|
296
|
-
// From the button, the default Tab lands exactly where leaving
|
|
297
|
-
// the picker should. Guard the METHOD, not just the element:
|
|
298
|
-
// this shape has bitten these helpers before.
|
|
299
|
-
button?.focus?.({ preventScroll: true });
|
|
300
|
-
closeList(false);
|
|
301
|
-
break;
|
|
302
|
-
default:
|
|
303
|
-
if (
|
|
304
|
-
event.key.length === 1 &&
|
|
305
|
-
!event.ctrlKey &&
|
|
306
|
-
!event.metaKey &&
|
|
307
|
-
!event.altKey
|
|
308
|
-
) {
|
|
309
|
-
runTypeahead(event.key);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
223
|
function onListClick(event) {
|
|
315
224
|
const li =
|
|
316
225
|
event.target && event.target.closest
|
|
@@ -335,7 +244,6 @@ function initTextSizePicker(root, opts = {}) {
|
|
|
335
244
|
|
|
336
245
|
button.addEventListener("click", onButtonClick);
|
|
337
246
|
button.addEventListener("keydown", onButtonKeydown);
|
|
338
|
-
list.addEventListener("keydown", onListKeydown);
|
|
339
247
|
list.addEventListener("click", onListClick);
|
|
340
248
|
root.addEventListener("focusout", onRootFocusOut);
|
|
341
249
|
document.addEventListener("click", onDocumentClick);
|
|
@@ -371,10 +279,9 @@ function initTextSizePicker(root, opts = {}) {
|
|
|
371
279
|
return {
|
|
372
280
|
setSize: applySize,
|
|
373
281
|
destroy: () => {
|
|
374
|
-
|
|
282
|
+
keyboard.destroy();
|
|
375
283
|
button.removeEventListener("click", onButtonClick);
|
|
376
284
|
button.removeEventListener("keydown", onButtonKeydown);
|
|
377
|
-
list.removeEventListener("keydown", onListKeydown);
|
|
378
285
|
list.removeEventListener("click", onListClick);
|
|
379
286
|
root.removeEventListener("focusout", onRootFocusOut);
|
|
380
287
|
document.removeEventListener("click", onDocumentClick);
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// lily-design-system-nunjucks-text-size-picker/text-size-picker.client.js
|
|
2
|
-
|
|
2
|
+
import { createListboxKeyboard } from "@lilydesignsystem/nunjucks-listbox-behavior";
|
|
3
3
|
function sizeName(size) {
|
|
4
4
|
return String(size || "").split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
5
5
|
}
|
|
@@ -16,11 +16,6 @@ function safeStorageSet(key, value) {
|
|
|
16
16
|
} catch (_e) {
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
-
function scrollIntoViewIfPossible(el) {
|
|
20
|
-
if (el && typeof el.scrollIntoView === "function") {
|
|
21
|
-
el.scrollIntoView({ block: "nearest" });
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
19
|
function initTextSizePicker(root, opts = {}) {
|
|
25
20
|
const noop = { setSize: () => {
|
|
26
21
|
}, destroy: () => {
|
|
@@ -32,15 +27,11 @@ function initTextSizePicker(root, opts = {}) {
|
|
|
32
27
|
if (!button || !list) return noop;
|
|
33
28
|
const options = Array.from(list.querySelectorAll('[role="option"]'));
|
|
34
29
|
const values = options.map((o) => o.getAttribute("data-value") || "");
|
|
35
|
-
const labels = options.map((o) => (o.textContent || "").trim());
|
|
36
30
|
const storageKey = root.getAttribute("data-lily-text-size-picker-storage-key") || "";
|
|
37
31
|
const defaultValue = root.getAttribute("data-lily-text-size-picker-default-value") || "";
|
|
38
32
|
const valueAttr = root.getAttribute("data-lily-text-size-picker-value") || "";
|
|
39
33
|
let current = "";
|
|
40
34
|
let open = false;
|
|
41
|
-
let activeIndex = -1;
|
|
42
|
-
let typeahead = "";
|
|
43
|
-
let typeaheadTimer;
|
|
44
35
|
let appliedValue = "";
|
|
45
36
|
function applySize(slug) {
|
|
46
37
|
if (!slug) return;
|
|
@@ -56,26 +47,13 @@ function initTextSizePicker(root, opts = {}) {
|
|
|
56
47
|
});
|
|
57
48
|
if (typeof opts.onChange === "function") opts.onChange(slug);
|
|
58
49
|
}
|
|
59
|
-
function setActive(index) {
|
|
60
|
-
activeIndex = index;
|
|
61
|
-
options.forEach((o, i) => {
|
|
62
|
-
if (i === index) o.setAttribute("data-active", "");
|
|
63
|
-
else o.removeAttribute("data-active");
|
|
64
|
-
});
|
|
65
|
-
if (index >= 0 && options[index]) {
|
|
66
|
-
list.setAttribute("aria-activedescendant", options[index].id);
|
|
67
|
-
scrollIntoViewIfPossible(options[index]);
|
|
68
|
-
} else {
|
|
69
|
-
list.removeAttribute("aria-activedescendant");
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
50
|
function openList(startIndex) {
|
|
73
51
|
const selected = values.indexOf(current);
|
|
74
52
|
const start = options.length === 0 ? -1 : typeof startIndex === "number" ? startIndex : selected >= 0 ? selected : 0;
|
|
75
53
|
open = true;
|
|
76
54
|
list.hidden = false;
|
|
77
55
|
button.setAttribute("aria-expanded", "true");
|
|
78
|
-
setActive(start);
|
|
56
|
+
keyboard.setActive(start);
|
|
79
57
|
list.focus({ preventScroll: true });
|
|
80
58
|
}
|
|
81
59
|
function closeList(refocus = true) {
|
|
@@ -83,7 +61,7 @@ function initTextSizePicker(root, opts = {}) {
|
|
|
83
61
|
open = false;
|
|
84
62
|
list.hidden = true;
|
|
85
63
|
button.setAttribute("aria-expanded", "false");
|
|
86
|
-
setActive(-1);
|
|
64
|
+
keyboard.setActive(-1);
|
|
87
65
|
if (refocus) button.focus({ preventScroll: true });
|
|
88
66
|
}
|
|
89
67
|
function choose(index) {
|
|
@@ -91,30 +69,18 @@ function initTextSizePicker(root, opts = {}) {
|
|
|
91
69
|
if (slug) applySize(slug);
|
|
92
70
|
closeList();
|
|
93
71
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
typeaheadTimer = setTimeout(() => {
|
|
105
|
-
typeahead = "";
|
|
106
|
-
}, TYPEAHEAD_RESET_MS);
|
|
107
|
-
const query = sameCharRun ? lower : typeahead;
|
|
108
|
-
const anchor = activeIndex < 0 ? 0 : activeIndex;
|
|
109
|
-
const start = sameCharRun ? anchor + 1 : anchor;
|
|
110
|
-
for (let n = 0; n < options.length; n++) {
|
|
111
|
-
const i = (start + n) % options.length;
|
|
112
|
-
if (labels[i].toLowerCase().startsWith(query)) {
|
|
113
|
-
setActive(i);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
72
|
+
const keyboard = createListboxKeyboard(list, {
|
|
73
|
+
clamp: true,
|
|
74
|
+
typeahead: true,
|
|
75
|
+
pageSize: 10,
|
|
76
|
+
onActivate: choose,
|
|
77
|
+
onEscape: () => closeList(),
|
|
78
|
+
onTabOut: () => {
|
|
79
|
+
var _a;
|
|
80
|
+
(_a = button == null ? void 0 : button.focus) == null ? void 0 : _a.call(button, { preventScroll: true });
|
|
81
|
+
closeList(false);
|
|
116
82
|
}
|
|
117
|
-
}
|
|
83
|
+
});
|
|
118
84
|
function onButtonClick() {
|
|
119
85
|
if (open) closeList();
|
|
120
86
|
else openList();
|
|
@@ -135,52 +101,6 @@ function initTextSizePicker(root, opts = {}) {
|
|
|
135
101
|
break;
|
|
136
102
|
}
|
|
137
103
|
}
|
|
138
|
-
function onListKeydown(event) {
|
|
139
|
-
var _a;
|
|
140
|
-
switch (event.key) {
|
|
141
|
-
case "ArrowDown":
|
|
142
|
-
event.preventDefault();
|
|
143
|
-
moveActive(1);
|
|
144
|
-
break;
|
|
145
|
-
case "ArrowUp":
|
|
146
|
-
event.preventDefault();
|
|
147
|
-
moveActive(-1);
|
|
148
|
-
break;
|
|
149
|
-
case "Home":
|
|
150
|
-
event.preventDefault();
|
|
151
|
-
setActive(0);
|
|
152
|
-
break;
|
|
153
|
-
case "End":
|
|
154
|
-
event.preventDefault();
|
|
155
|
-
setActive(options.length - 1);
|
|
156
|
-
break;
|
|
157
|
-
case "Enter":
|
|
158
|
-
case " ":
|
|
159
|
-
event.preventDefault();
|
|
160
|
-
if (activeIndex >= 0) choose(activeIndex);
|
|
161
|
-
break;
|
|
162
|
-
case "Escape":
|
|
163
|
-
event.preventDefault();
|
|
164
|
-
closeList();
|
|
165
|
-
break;
|
|
166
|
-
case "PageUp":
|
|
167
|
-
event.preventDefault();
|
|
168
|
-
moveActive(-10);
|
|
169
|
-
break;
|
|
170
|
-
case "PageDown":
|
|
171
|
-
event.preventDefault();
|
|
172
|
-
moveActive(10);
|
|
173
|
-
break;
|
|
174
|
-
case "Tab":
|
|
175
|
-
(_a = button == null ? void 0 : button.focus) == null ? void 0 : _a.call(button, { preventScroll: true });
|
|
176
|
-
closeList(false);
|
|
177
|
-
break;
|
|
178
|
-
default:
|
|
179
|
-
if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
|
|
180
|
-
runTypeahead(event.key);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
104
|
function onListClick(event) {
|
|
185
105
|
const li = event.target && event.target.closest ? event.target.closest('[role="option"]') : null;
|
|
186
106
|
if (!li) return;
|
|
@@ -199,7 +119,6 @@ function initTextSizePicker(root, opts = {}) {
|
|
|
199
119
|
}
|
|
200
120
|
button.addEventListener("click", onButtonClick);
|
|
201
121
|
button.addEventListener("keydown", onButtonKeydown);
|
|
202
|
-
list.addEventListener("keydown", onListKeydown);
|
|
203
122
|
list.addEventListener("click", onListClick);
|
|
204
123
|
root.addEventListener("focusout", onRootFocusOut);
|
|
205
124
|
document.addEventListener("click", onDocumentClick);
|
|
@@ -213,10 +132,9 @@ function initTextSizePicker(root, opts = {}) {
|
|
|
213
132
|
return {
|
|
214
133
|
setSize: applySize,
|
|
215
134
|
destroy: () => {
|
|
216
|
-
|
|
135
|
+
keyboard.destroy();
|
|
217
136
|
button.removeEventListener("click", onButtonClick);
|
|
218
137
|
button.removeEventListener("keydown", onButtonKeydown);
|
|
219
|
-
list.removeEventListener("keydown", onListKeydown);
|
|
220
138
|
list.removeEventListener("click", onListClick);
|
|
221
139
|
root.removeEventListener("focusout", onRootFocusOut);
|
|
222
140
|
document.removeEventListener("click", onDocumentClick);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lilydesignsystem/nunjucks-text-size-picker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": "=26"
|
|
6
6
|
},
|
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"prepublishOnly": "cd .. && npm run build"
|
|
26
26
|
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@lilydesignsystem/nunjucks-listbox-behavior": "^0.1.0"
|
|
29
|
+
},
|
|
27
30
|
"keywords": [
|
|
28
31
|
"lily",
|
|
29
32
|
"design",
|