@lilydesignsystem/angular-text-size-picker 0.1.0 → 0.1.2

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.
@@ -1,6 +1,7 @@
1
1
  import { NgTemplateOutlet } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { Directive, input, model, output, contentChild, TemplateRef, viewChild, signal, computed, inject, ChangeDetectorRef, DestroyRef, effect, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { Directive, input, model, output, contentChild, TemplateRef, viewChild, signal, computed, inject, ChangeDetectorRef, effect, ChangeDetectionStrategy, Component } from '@angular/core';
4
+ import { IconButton, Listbox } from '@lilydesignsystem/angular-headless';
4
5
 
5
6
  /**
6
7
  * Resolve a size slug to its display label: each hyphen-separated word
@@ -36,10 +37,10 @@ class TextSizePickerIcon {
36
37
  static ngTemplateContextGuard(_dir, _ctx) {
37
38
  return true;
38
39
  }
39
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: TextSizePickerIcon, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
40
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: TextSizePickerIcon, isStandalone: true, selector: "ng-template[lilyTextSizePickerIcon]", ngImport: i0 }); }
40
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TextSizePickerIcon, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
41
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: TextSizePickerIcon, isStandalone: true, selector: "ng-template[lilyTextSizePickerIcon]", ngImport: i0 }); }
41
42
  }
42
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: TextSizePickerIcon, decorators: [{
43
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TextSizePickerIcon, decorators: [{
43
44
  type: Directive,
44
45
  args: [{
45
46
  selector: "ng-template[lilyTextSizePickerIcon]",
@@ -82,6 +83,9 @@ class TextSizePicker {
82
83
  ...(ngDevMode ? [{ debugName: "iconTemplate" }] : /* istanbul ignore next */ []));
83
84
  this.rootRef = viewChild.required("rootEl", /* @ts-ignore */
84
85
  ...(ngDevMode ? [{ debugName: "rootRef" }] : /* istanbul ignore next */ []));
86
+ // Angular resolves a template-ref-variable on a component tag to the
87
+ // component INSTANCE by default (not its ElementRef) — exactly what's
88
+ // needed to call the headless components' own public `focus()` methods.
85
89
  this.buttonRef = viewChild.required("buttonEl", /* @ts-ignore */
86
90
  ...(ngDevMode ? [{ debugName: "buttonRef" }] : /* istanbul ignore next */ []));
87
91
  this.listRef = viewChild.required("listEl", /* @ts-ignore */
@@ -92,12 +96,6 @@ class TextSizePicker {
92
96
  ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
93
97
  this.activeIndex = signal(-1, /* @ts-ignore */
94
98
  ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
95
- /** `aria-activedescendant` is only meaningful while the listbox is open. */
96
- this.activeDescendant = computed(() => {
97
- const i = this.activeIndex();
98
- return this.open() && i >= 0 ? this.optionId(i) : null;
99
- }, /* @ts-ignore */
100
- ...(ngDevMode ? [{ debugName: "activeDescendant" }] : /* istanbul ignore next */ []));
101
99
  this.childContext = computed(() => {
102
100
  const args = {
103
101
  value: this.value(),
@@ -107,8 +105,6 @@ class TextSizePicker {
107
105
  return { $implicit: args, ...args };
108
106
  }, /* @ts-ignore */
109
107
  ...(ngDevMode ? [{ debugName: "childContext" }] : /* istanbul ignore next */ []));
110
- // Typeahead buffer: APG listbox behaviour. Reset after a pause.
111
- this.typeahead = "";
112
108
  this.initialised = false;
113
109
  // ---------------------------------------------------------------
114
110
  // Open / close
@@ -119,7 +115,17 @@ class TextSizePicker {
119
115
  * Flushing detection first is the Angular equivalent of Svelte's
120
116
  * synchronous update (same fix the date-time-picker port recorded). */
121
117
  this.cdr = inject(ChangeDetectorRef);
122
- inject(DestroyRef).onDestroy(() => clearTimeout(this.typeaheadTimer));
118
+ // Arrow/Home/End/PageUp/PageDown/typeahead/Escape/Tab keyboard handling
119
+ // inside the open list is owned by the composed `lily-listbox`'s
120
+ // `navigation="active-descendant"` mode (see
121
+ // @lilydesignsystem/angular-headless); this component only decides
122
+ // what open/close/choose/scroll mean, and keeps the highlighted option
123
+ // in view whenever activeIndex changes.
124
+ this.scrollOnActiveIndexChange = effect(() => {
125
+ this.activeIndex();
126
+ this.scrollActiveIntoView();
127
+ }, /* @ts-ignore */
128
+ ...(ngDevMode ? [{ debugName: "scrollOnActiveIndexChange" }] : /* istanbul ignore next */ []));
123
129
  effect(() => {
124
130
  const current = this.value();
125
131
  if (!this.initialised) {
@@ -181,7 +187,7 @@ class TextSizePicker {
181
187
  // aria-activedescendant, per the APG listbox pattern.
182
188
  this.cdr.detectChanges();
183
189
  queueMicrotask(() => {
184
- this.listRef().nativeElement.focus({ preventScroll: true });
190
+ this.listRef().focus({ preventScroll: true });
185
191
  this.scrollActiveIntoView();
186
192
  });
187
193
  }
@@ -192,7 +198,7 @@ class TextSizePicker {
192
198
  this.open.set(false);
193
199
  this.activeIndex.set(-1);
194
200
  if (refocus)
195
- queueMicrotask(() => this.buttonRef().nativeElement.focus({ preventScroll: true }));
201
+ queueMicrotask(() => this.buttonRef().focus({ preventScroll: true }));
196
202
  }
197
203
  choose(index) {
198
204
  const slug = this.sizes()[index];
@@ -204,43 +210,15 @@ class TextSizePicker {
204
210
  const i = this.activeIndex();
205
211
  if (i < 0)
206
212
  return;
207
- const el = this.listRef().nativeElement.children[i];
213
+ // getElementById rather than reaching into the composed Listbox's
214
+ // DOM: Listbox exposes behaviour (focus()) and state (activeIndex),
215
+ // not its rendered children, the same "consumer owns the option
216
+ // elements" division of responsibility the headless component
217
+ // documents for itself.
218
+ const el = document.getElementById(this.optionId(i));
208
219
  // jsdom does not implement scrollIntoView; call it only if present.
209
220
  el?.scrollIntoView?.({ block: "nearest" });
210
221
  }
211
- moveActive(delta) {
212
- const count = this.sizes().length;
213
- if (count === 0)
214
- return;
215
- // Clamp rather than wrap, per the APG listbox pattern.
216
- this.activeIndex.set(Math.min(Math.max(this.activeIndex() + delta, 0), count - 1));
217
- this.scrollActiveIntoView();
218
- }
219
- runTypeahead(char) {
220
- const lower = char.toLowerCase();
221
- // APG listbox typeahead: a single character moves to the NEXT
222
- // option starting with it, and repeating that character keeps
223
- // cycling. Only a buffer of differing characters refines the
224
- // match, and that buffer stays anchored on the active option.
225
- const sameCharRun = this.typeahead === "" || [...this.typeahead].every((c) => c === lower);
226
- this.typeahead += lower;
227
- clearTimeout(this.typeaheadTimer);
228
- this.typeaheadTimer = setTimeout(() => (this.typeahead = ""), 500);
229
- const query = sameCharRun ? lower : this.typeahead;
230
- const sizes = this.sizes();
231
- const anchor = this.activeIndex() < 0 ? 0 : this.activeIndex();
232
- const start = sameCharRun ? anchor + 1 : anchor;
233
- // Search forward, wrapping once — typeahead wraps even though the
234
- // arrows clamp, or options above the cursor would be untypable.
235
- for (let n = 0; n < sizes.length; n++) {
236
- const i = (start + n) % sizes.length;
237
- if (this.labelFor(sizes[i]).toLowerCase().startsWith(query)) {
238
- this.activeIndex.set(i);
239
- this.scrollActiveIntoView();
240
- return;
241
- }
242
- }
243
- }
244
222
  // ---------------------------------------------------------------
245
223
  // Keyboard
246
224
  // ---------------------------------------------------------------
@@ -258,66 +236,14 @@ class TextSizePicker {
258
236
  break;
259
237
  }
260
238
  }
261
- onListKeydown(event) {
262
- switch (event.key) {
263
- case "ArrowDown":
264
- event.preventDefault();
265
- this.moveActive(1);
266
- break;
267
- case "ArrowUp":
268
- event.preventDefault();
269
- this.moveActive(-1);
270
- break;
271
- case "Home":
272
- event.preventDefault();
273
- this.activeIndex.set(0);
274
- this.scrollActiveIntoView();
275
- break;
276
- case "End":
277
- event.preventDefault();
278
- this.activeIndex.set(this.sizes().length - 1);
279
- this.scrollActiveIntoView();
280
- break;
281
- case "Enter":
282
- case " ":
283
- event.preventDefault();
284
- if (this.activeIndex() >= 0)
285
- this.choose(this.activeIndex());
286
- break;
287
- case "Escape":
288
- event.preventDefault();
289
- this.closeList();
290
- break;
291
- case "PageUp":
292
- event.preventDefault();
293
- this.moveActive(-10);
294
- break;
295
- case "PageDown":
296
- // ±10, clamped: an APG-optional key for long size lists.
297
- event.preventDefault();
298
- this.moveActive(10);
299
- break;
300
- case "Tab":
301
- // Tab moves on — but focus goes to the button FIRST, without
302
- // cancelling the key. Hiding the focused list drops focus to
303
- // <body>, and the browser then computes the default Tab move
304
- // from the top of the document, so tabbing out of an open
305
- // picker teleported the user to the page's first tab stop.
306
- // From the button, the default Tab lands exactly where leaving
307
- // the picker should. The button always exists, so no
308
- // detectChanges is needed before the focus move; guard the
309
- // METHOD because jsdom-shaped hosts may not implement it.
310
- this.buttonRef().nativeElement.focus?.({ preventScroll: true });
311
- this.closeList(false);
312
- break;
313
- default:
314
- if (event.key.length === 1 &&
315
- !event.ctrlKey &&
316
- !event.metaKey &&
317
- !event.altKey) {
318
- this.runTypeahead(event.key);
319
- }
320
- }
239
+ /** `lily-listbox` Tab handling never prevents the key — focus goes to
240
+ * the button FIRST so the browser's default Tab move computes from
241
+ * the picker's position rather than from <body> (hiding the focused
242
+ * list drops focus there, and the browser would otherwise compute the
243
+ * default Tab move from the top of the document). */
244
+ onListTabOut() {
245
+ this.buttonRef().focus({ preventScroll: true });
246
+ this.closeList(false);
321
247
  }
322
248
  onRootFocusOut(event) {
323
249
  const next = event.relatedTarget;
@@ -350,8 +276,8 @@ class TextSizePicker {
350
276
  }
351
277
  this.sizeChange.emit(slug);
352
278
  }
353
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: TextSizePicker, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
354
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: TextSizePicker, isStandalone: true, selector: "lily-text-size-picker", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, sizes: { classPropertyName: "sizes", publicName: "sizes", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: true, isRequired: false, transformFunction: null }, storageKey: { classPropertyName: "storageKey", publicName: "storageKey", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, sizeLabels: { classPropertyName: "sizeLabels", publicName: "sizeLabels", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", sizeChange: "sizeChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, queries: [{ propertyName: "iconTemplate", first: true, predicate: TemplateRef, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "rootRef", first: true, predicate: ["rootEl"], descendants: true, isSignal: true }, { propertyName: "buttonRef", first: true, predicate: ["buttonEl"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
279
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TextSizePicker, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
280
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: TextSizePicker, isStandalone: true, selector: "lily-text-size-picker", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, sizes: { classPropertyName: "sizes", publicName: "sizes", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: true, isRequired: false, transformFunction: null }, storageKey: { classPropertyName: "storageKey", publicName: "storageKey", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, sizeLabels: { classPropertyName: "sizeLabels", publicName: "sizeLabels", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", sizeChange: "sizeChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, queries: [{ propertyName: "iconTemplate", first: true, predicate: TemplateRef, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "rootRef", first: true, predicate: ["rootEl"], descendants: true, isSignal: true }, { propertyName: "buttonRef", first: true, predicate: ["buttonEl"], descendants: true, isSignal: true }, { propertyName: "listRef", first: true, predicate: ["listEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
355
281
  <div
356
282
  #rootEl
357
283
  class="text-size-picker {{ className() }}"
@@ -359,14 +285,13 @@ class TextSizePicker {
359
285
  >
360
286
  <input type="hidden" [name]="name()" [value]="value()" />
361
287
 
362
- <button
288
+ <lily-icon-button
363
289
  #buttonEl
364
- type="button"
365
- class="text-size-picker-button"
366
- [attr.aria-label]="label() || null"
367
- aria-haspopup="listbox"
368
- [attr.aria-expanded]="open()"
369
- [attr.aria-controls]="listId"
290
+ [label]="label()"
291
+ baseClass="text-size-picker-button"
292
+ ariaHaspopup="listbox"
293
+ [ariaExpanded]="open()"
294
+ [ariaControls]="listId"
370
295
  (click)="toggle()"
371
296
  (keydown)="onButtonKeydown($event)"
372
297
  >
@@ -391,18 +316,22 @@ class TextSizePicker {
391
316
  <path d="M4 13 7.2 3h1.6L12 13M5.4 9.5h5.2" />
392
317
  </svg>
393
318
  }
394
- </button>
319
+ </lily-icon-button>
395
320
 
396
- <ul
321
+ <lily-listbox
397
322
  #listEl
398
- class="text-size-picker-list"
399
- [id]="listId"
400
- role="listbox"
401
- [attr.aria-label]="label() || null"
402
- [attr.aria-activedescendant]="activeDescendant()"
403
- tabindex="-1"
404
- [attr.hidden]="open() ? null : ''"
405
- (keydown)="onListKeydown($event)"
323
+ [label]="label()"
324
+ baseClass="text-size-picker-list"
325
+ [elementId]="listId"
326
+ navigation="active-descendant"
327
+ [clamp]="true"
328
+ [typeahead]="true"
329
+ [pageSize]="10"
330
+ [(activeIndex)]="activeIndex"
331
+ [hidden]="!open()"
332
+ (activate)="choose($event)"
333
+ (escape)="closeList()"
334
+ (tabOut)="onListTabOut()"
406
335
  >
407
336
  @for (size of sizes(); track size; let i = $index) {
408
337
  <li
@@ -416,16 +345,16 @@ class TextSizePicker {
416
345
  {{ labelFor(size) }}
417
346
  </li>
418
347
  }
419
- </ul>
348
+ </lily-listbox>
420
349
  </div>
421
- `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
350
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconButton, selector: "lily-icon-button", inputs: ["label", "className", "baseClass", "ariaHaspopup", "ariaExpanded", "ariaControls", "tabIndex", "disabled"] }, { kind: "component", type: Listbox, selector: "lily-listbox", inputs: ["label", "className", "baseClass", "elementId", "hidden", "navigation", "clamp", "typeahead", "pageSize", "activeIndex"], outputs: ["activeIndexChange", "activate", "escape", "tabOut"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
422
351
  }
423
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: TextSizePicker, decorators: [{
352
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TextSizePicker, decorators: [{
424
353
  type: Component,
425
354
  args: [{
426
355
  selector: "lily-text-size-picker",
427
356
  standalone: true,
428
- imports: [NgTemplateOutlet],
357
+ imports: [NgTemplateOutlet, IconButton, Listbox],
429
358
  changeDetection: ChangeDetectionStrategy.OnPush,
430
359
  host: {
431
360
  "(document:click)": "onDocumentClick($event)",
@@ -438,14 +367,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
438
367
  >
439
368
  <input type="hidden" [name]="name()" [value]="value()" />
440
369
 
441
- <button
370
+ <lily-icon-button
442
371
  #buttonEl
443
- type="button"
444
- class="text-size-picker-button"
445
- [attr.aria-label]="label() || null"
446
- aria-haspopup="listbox"
447
- [attr.aria-expanded]="open()"
448
- [attr.aria-controls]="listId"
372
+ [label]="label()"
373
+ baseClass="text-size-picker-button"
374
+ ariaHaspopup="listbox"
375
+ [ariaExpanded]="open()"
376
+ [ariaControls]="listId"
449
377
  (click)="toggle()"
450
378
  (keydown)="onButtonKeydown($event)"
451
379
  >
@@ -470,18 +398,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
470
398
  <path d="M4 13 7.2 3h1.6L12 13M5.4 9.5h5.2" />
471
399
  </svg>
472
400
  }
473
- </button>
401
+ </lily-icon-button>
474
402
 
475
- <ul
403
+ <lily-listbox
476
404
  #listEl
477
- class="text-size-picker-list"
478
- [id]="listId"
479
- role="listbox"
480
- [attr.aria-label]="label() || null"
481
- [attr.aria-activedescendant]="activeDescendant()"
482
- tabindex="-1"
483
- [attr.hidden]="open() ? null : ''"
484
- (keydown)="onListKeydown($event)"
405
+ [label]="label()"
406
+ baseClass="text-size-picker-list"
407
+ [elementId]="listId"
408
+ navigation="active-descendant"
409
+ [clamp]="true"
410
+ [typeahead]="true"
411
+ [pageSize]="10"
412
+ [(activeIndex)]="activeIndex"
413
+ [hidden]="!open()"
414
+ (activate)="choose($event)"
415
+ (escape)="closeList()"
416
+ (tabOut)="onListTabOut()"
485
417
  >
486
418
  @for (size of sizes(); track size; let i = $index) {
487
419
  <li
@@ -495,7 +427,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImpor
495
427
  {{ labelFor(size) }}
496
428
  </li>
497
429
  }
498
- </ul>
430
+ </lily-listbox>
499
431
  </div>
500
432
  `,
501
433
  }]
@@ -1 +1 @@
1
- {"version":3,"file":"lilydesignsystem-angular-text-size-picker.mjs","sources":["../../text-size-picker.component.ts","../../lilydesignsystem-angular-text-size-picker.ts"],"sourcesContent":["import { NgTemplateOutlet } from \"@angular/common\";\nimport {\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n DestroyRef,\n Directive,\n ElementRef,\n TemplateRef,\n computed,\n contentChild,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n viewChild,\n} from \"@angular/core\";\n\n/**\n * Default button icon: a bundled SVG (a stroke-drawn \"A\"), not a\n * Unicode character. Reversed 2026-09-16 from the font-dependent-glyph\n * convention (was the plain letter U+0041, exported as\n * `LATIN_CAPITAL_LETTER_A` — removed, not renamed). \"A\" itself needed\n * no escaping and had no font-fallback risk, but it still varied in\n * weight and proportions across font stacks; a bundled outline SVG\n * matches the other four picker icons as one consistent visual family\n * regardless of the consumer's fonts.\n */\n\n/** Context passed to a custom icon `<ng-template>` (the button icon). */\nexport type ChildArgs = {\n /** Currently selected size slug. */\n value: string;\n /** Is the listbox open? */\n open: boolean;\n /** Resolve a slug to its display label. */\n labelFor: (size: string) => string;\n};\n\n/**\n * Resolve a size slug to its display label: each hyphen-separated word\n * title-cased, so a slug like \"x-large\" renders as \"X Large\". Mirrors\n * `themeName` in theme-picker and `localeName` in locale-picker.\n */\nexport function sizeName(size: string): string {\n return size\n .split(\"-\")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\" \");\n}\n\nlet uid = 0;\n/** Stable per-instance id prefix; SSR-safe (no Math.random / Date.now). */\nexport function nextTextSizePickerId(): string {\n uid += 1;\n return `text-size-picker-${uid}`;\n}\n\n/**\n * Optional marker for the projected icon template. Gives consumers typed\n * `let-` variables:\n *\n * ```html\n * <lily-text-size-picker ...>\n * <ng-template lilyTextSizePickerIcon let-args>{{ args.labelFor(args.value) }}</ng-template>\n * </lily-text-size-picker>\n * ```\n *\n * The component queries any projected `<ng-template>`, so the marker is\n * for type-checking and readability, not for matching.\n */\n@Directive({\n selector: \"ng-template[lilyTextSizePickerIcon]\",\n standalone: true,\n})\nexport class TextSizePickerIcon {\n static ngTemplateContextGuard(\n _dir: TextSizePickerIcon,\n _ctx: unknown,\n ): _ctx is ChildArgs & { $implicit: ChildArgs } {\n return true;\n }\n}\n\n/**\n * TextSizePicker — `data-text-size` text-size picker.\n *\n * Renders an icon button that opens a WAI-ARIA APG listbox of sizes. On\n * every size change the component sets `data-text-size=\"{slug}\"` on the\n * document root (or on a consumer-supplied target), with optional\n * `localStorage` persistence. Ships no CSS; the consumer maps each\n * `[data-text-size=\"…\"]` slug to real typography. See `spec/index.md`\n * for the full contract.\n */\n@Component({\n selector: \"lily-text-size-picker\",\n standalone: true,\n imports: [NgTemplateOutlet],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n \"(document:click)\": \"onDocumentClick($event)\",\n },\n template: `\n <div\n #rootEl\n class=\"text-size-picker {{ className() }}\"\n (focusout)=\"onRootFocusOut($event)\"\n >\n <input type=\"hidden\" [name]=\"name()\" [value]=\"value()\" />\n\n <button\n #buttonEl\n type=\"button\"\n class=\"text-size-picker-button\"\n [attr.aria-label]=\"label() || null\"\n aria-haspopup=\"listbox\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-controls]=\"listId\"\n (click)=\"toggle()\"\n (keydown)=\"onButtonKeydown($event)\"\n >\n @if (iconTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"childContext()\"\n />\n } @else {\n <svg\n class=\"text-size-picker-icon\"\n viewBox=\"0 0 16 16\"\n width=\"1.05rem\"\n height=\"1.05rem\"\n aria-hidden=\"true\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.6\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"M4 13 7.2 3h1.6L12 13M5.4 9.5h5.2\" />\n </svg>\n }\n </button>\n\n <ul\n #listEl\n class=\"text-size-picker-list\"\n [id]=\"listId\"\n role=\"listbox\"\n [attr.aria-label]=\"label() || null\"\n [attr.aria-activedescendant]=\"activeDescendant()\"\n tabindex=\"-1\"\n [attr.hidden]=\"open() ? null : ''\"\n (keydown)=\"onListKeydown($event)\"\n >\n @for (size of sizes(); track size; let i = $index) {\n <li\n class=\"text-size-picker-option\"\n [id]=\"optionId(i)\"\n role=\"option\"\n [attr.aria-selected]=\"size === value()\"\n [attr.data-active]=\"i === activeIndex() ? '' : null\"\n (click)=\"choose(i)\"\n >\n {{ labelFor(size) }}\n </li>\n }\n </ul>\n </div>\n `,\n})\nexport class TextSizePicker {\n readonly label = input.required<string>();\n readonly sizes = input.required<string[]>();\n readonly value = model<string>(\"\");\n readonly defaultValue = input<string>(\"\");\n readonly storageKey = input<string>(\"\");\n readonly name = input<string>(\"text-size\");\n readonly target = input<HTMLElement | null>(null);\n readonly sizeLabels = input<Record<string, string>>({});\n readonly className = input<string>(\"\");\n readonly sizeChange = output<string>();\n\n /** Projected icon template; replaces the default icon when supplied. */\n protected readonly iconTemplate = contentChild(TemplateRef);\n\n private readonly rootRef =\n viewChild.required<ElementRef<HTMLDivElement>>(\"rootEl\");\n private readonly buttonRef =\n viewChild.required<ElementRef<HTMLButtonElement>>(\"buttonEl\");\n private readonly listRef =\n viewChild.required<ElementRef<HTMLUListElement>>(\"listEl\");\n\n private readonly baseId = nextTextSizePickerId();\n protected readonly listId = `${this.baseId}-list`;\n\n protected readonly open = signal(false);\n protected readonly activeIndex = signal(-1);\n\n /** `aria-activedescendant` is only meaningful while the listbox is open. */\n protected readonly activeDescendant = computed(() => {\n const i = this.activeIndex();\n return this.open() && i >= 0 ? this.optionId(i) : null;\n });\n\n protected readonly childContext = computed(() => {\n const args: ChildArgs = {\n value: this.value(),\n open: this.open(),\n labelFor: (size: string) => this.labelFor(size),\n };\n return { $implicit: args, ...args };\n });\n\n // Typeahead buffer: APG listbox behaviour. Reset after a pause.\n private typeahead = \"\";\n private typeaheadTimer: ReturnType<typeof setTimeout> | undefined;\n\n private initialised = false;\n\n constructor() {\n inject(DestroyRef).onDestroy(() => clearTimeout(this.typeaheadTimer));\n\n effect(() => {\n const current = this.value();\n\n if (!this.initialised) {\n this.initialised = true;\n let initial = current;\n\n const sk = this.storageKey();\n if (!initial && sk) {\n try {\n initial =\n (typeof localStorage !== \"undefined\"\n ? localStorage.getItem(sk)\n : null) ?? \"\";\n } catch {\n // ignore privacy errors\n }\n }\n\n if (!initial) {\n const sizes = this.sizes();\n const dv = this.defaultValue();\n initial =\n dv || (sizes.includes(\"medium\") ? \"medium\" : sizes[0]) || \"\";\n }\n\n if (initial && initial !== current) {\n this.value.set(initial);\n return;\n }\n }\n\n if (current) this.applySize(current);\n });\n }\n\n protected optionId(index: number): string {\n return `${this.baseId}-option-${index}`;\n }\n\n labelFor(size: string): string {\n const labels = this.sizeLabels();\n if (size in labels) return labels[size];\n return sizeName(size);\n }\n\n // ---------------------------------------------------------------\n // Open / close\n // ---------------------------------------------------------------\n\n /** Under zoneless change detection the signal write that un-hides the\n * list has not reached the DOM when a microtask runs, so focusing the\n * still-hidden list fails silently and Escape lands on the button.\n * Flushing detection first is the Angular equivalent of Svelte's\n * synchronous update (same fix the date-time-picker port recorded). */\n private readonly cdr = inject(ChangeDetectorRef);\n\n protected toggle(): void {\n if (this.open()) this.closeList();\n else this.openList();\n }\n\n /** Open the listbox, activating `startIndex` (default: the selection). */\n openList(startIndex?: number): void {\n const selected = this.sizes().indexOf(this.value());\n // An empty list has no option to activate; -1 keeps\n // aria-activedescendant off rather than pointing at an id that\n // does not exist.\n this.activeIndex.set(\n this.sizes().length === 0\n ? -1\n : (startIndex ?? (selected >= 0 ? selected : 0)),\n );\n this.open.set(true);\n // Focus moves to the listbox; the active option is conveyed via\n // aria-activedescendant, per the APG listbox pattern.\n this.cdr.detectChanges();\n queueMicrotask(() => {\n this.listRef().nativeElement.focus({ preventScroll: true });\n this.scrollActiveIntoView();\n });\n }\n\n /** Close the listbox; `refocus` returns focus to the button. */\n closeList(refocus = true): void {\n if (!this.open()) return;\n this.open.set(false);\n this.activeIndex.set(-1);\n if (refocus) queueMicrotask(() => this.buttonRef().nativeElement.focus({ preventScroll: true }));\n }\n\n protected choose(index: number): void {\n const slug = this.sizes()[index];\n if (slug) this.value.set(slug);\n this.closeList();\n }\n\n private scrollActiveIntoView(): void {\n const i = this.activeIndex();\n if (i < 0) return;\n const el = this.listRef().nativeElement.children[i] as\n HTMLElement | undefined;\n // jsdom does not implement scrollIntoView; call it only if present.\n el?.scrollIntoView?.({ block: \"nearest\" });\n }\n\n private moveActive(delta: number): void {\n const count = this.sizes().length;\n if (count === 0) return;\n // Clamp rather than wrap, per the APG listbox pattern.\n this.activeIndex.set(\n Math.min(Math.max(this.activeIndex() + delta, 0), count - 1),\n );\n this.scrollActiveIntoView();\n }\n\n private runTypeahead(char: string): void {\n const lower = char.toLowerCase();\n // APG listbox typeahead: a single character moves to the NEXT\n // option starting with it, and repeating that character keeps\n // cycling. Only a buffer of differing characters refines the\n // match, and that buffer stays anchored on the active option.\n const sameCharRun =\n this.typeahead === \"\" || [...this.typeahead].every((c) => c === lower);\n this.typeahead += lower;\n clearTimeout(this.typeaheadTimer);\n this.typeaheadTimer = setTimeout(() => (this.typeahead = \"\"), 500);\n const query = sameCharRun ? lower : this.typeahead;\n const sizes = this.sizes();\n const anchor = this.activeIndex() < 0 ? 0 : this.activeIndex();\n const start = sameCharRun ? anchor + 1 : anchor;\n // Search forward, wrapping once — typeahead wraps even though the\n // arrows clamp, or options above the cursor would be untypable.\n for (let n = 0; n < sizes.length; n++) {\n const i = (start + n) % sizes.length;\n if (this.labelFor(sizes[i]).toLowerCase().startsWith(query)) {\n this.activeIndex.set(i);\n this.scrollActiveIntoView();\n return;\n }\n }\n }\n\n // ---------------------------------------------------------------\n // Keyboard\n // ---------------------------------------------------------------\n\n protected onButtonKeydown(event: KeyboardEvent): void {\n switch (event.key) {\n case \"ArrowDown\":\n case \"Enter\":\n case \" \":\n event.preventDefault();\n this.openList();\n break;\n case \"ArrowUp\":\n event.preventDefault();\n this.openList(this.sizes().length - 1);\n break;\n }\n }\n\n protected onListKeydown(event: KeyboardEvent): void {\n switch (event.key) {\n case \"ArrowDown\":\n event.preventDefault();\n this.moveActive(1);\n break;\n case \"ArrowUp\":\n event.preventDefault();\n this.moveActive(-1);\n break;\n case \"Home\":\n event.preventDefault();\n this.activeIndex.set(0);\n this.scrollActiveIntoView();\n break;\n case \"End\":\n event.preventDefault();\n this.activeIndex.set(this.sizes().length - 1);\n this.scrollActiveIntoView();\n break;\n case \"Enter\":\n case \" \":\n event.preventDefault();\n if (this.activeIndex() >= 0) this.choose(this.activeIndex());\n break;\n case \"Escape\":\n event.preventDefault();\n this.closeList();\n break;\n case \"PageUp\":\n event.preventDefault();\n this.moveActive(-10);\n break;\n case \"PageDown\":\n // ±10, clamped: an APG-optional key for long size lists.\n event.preventDefault();\n this.moveActive(10);\n break;\n case \"Tab\":\n // Tab moves on — but focus goes to the button FIRST, without\n // cancelling the key. Hiding the focused list drops focus to\n // <body>, and the browser then computes the default Tab move\n // from the top of the document, so tabbing out of an open\n // picker teleported the user to the page's first tab stop.\n // From the button, the default Tab lands exactly where leaving\n // the picker should. The button always exists, so no\n // detectChanges is needed before the focus move; guard the\n // METHOD because jsdom-shaped hosts may not implement it.\n this.buttonRef().nativeElement.focus?.({ preventScroll: true });\n this.closeList(false);\n break;\n default:\n if (\n event.key.length === 1 &&\n !event.ctrlKey &&\n !event.metaKey &&\n !event.altKey\n ) {\n this.runTypeahead(event.key);\n }\n }\n }\n\n protected onRootFocusOut(event: FocusEvent): void {\n const next = event.relatedTarget as Node | null;\n if (next && this.rootRef().nativeElement.contains(next)) return;\n this.closeList(false);\n }\n\n protected onDocumentClick(event: Event): void {\n if (!this.open()) return;\n const t = event.target as Node | null;\n if (t && !this.rootRef().nativeElement.contains(t)) this.closeList(false);\n }\n\n // ---------------------------------------------------------------\n // Apply\n // ---------------------------------------------------------------\n\n private applySize(slug: string): void {\n if (typeof document === \"undefined\" || !slug) return;\n (this.target() ?? document.documentElement).setAttribute(\n \"data-text-size\",\n slug,\n );\n\n const sk = this.storageKey();\n if (sk) {\n try {\n localStorage.setItem(sk, slug);\n } catch {\n // ignore quota / privacy errors\n }\n }\n this.sizeChange.emit(slug);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lilydesignsystem-angular-text-size-picker.mjs","sources":["../../text-size-picker.component.ts","../../lilydesignsystem-angular-text-size-picker.ts"],"sourcesContent":["import { NgTemplateOutlet } from \"@angular/common\";\nimport {\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n Directive,\n ElementRef,\n TemplateRef,\n computed,\n contentChild,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n viewChild,\n} from \"@angular/core\";\nimport { IconButton, Listbox } from \"@lilydesignsystem/angular-headless\";\n\n/**\n * Default button icon: a bundled SVG (a stroke-drawn \"A\"), not a\n * Unicode character. Reversed 2026-09-16 from the font-dependent-glyph\n * convention (was the plain letter U+0041, exported as\n * `LATIN_CAPITAL_LETTER_A` — removed, not renamed). \"A\" itself needed\n * no escaping and had no font-fallback risk, but it still varied in\n * weight and proportions across font stacks; a bundled outline SVG\n * matches the other four picker icons as one consistent visual family\n * regardless of the consumer's fonts.\n */\n\n/** Context passed to a custom icon `<ng-template>` (the button icon). */\nexport type ChildArgs = {\n /** Currently selected size slug. */\n value: string;\n /** Is the listbox open? */\n open: boolean;\n /** Resolve a slug to its display label. */\n labelFor: (size: string) => string;\n};\n\n/**\n * Resolve a size slug to its display label: each hyphen-separated word\n * title-cased, so a slug like \"x-large\" renders as \"X Large\". Mirrors\n * `themeName` in theme-picker and `localeName` in locale-picker.\n */\nexport function sizeName(size: string): string {\n return size\n .split(\"-\")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\" \");\n}\n\nlet uid = 0;\n/** Stable per-instance id prefix; SSR-safe (no Math.random / Date.now). */\nexport function nextTextSizePickerId(): string {\n uid += 1;\n return `text-size-picker-${uid}`;\n}\n\n/**\n * Optional marker for the projected icon template. Gives consumers typed\n * `let-` variables:\n *\n * ```html\n * <lily-text-size-picker ...>\n * <ng-template lilyTextSizePickerIcon let-args>{{ args.labelFor(args.value) }}</ng-template>\n * </lily-text-size-picker>\n * ```\n *\n * The component queries any projected `<ng-template>`, so the marker is\n * for type-checking and readability, not for matching.\n */\n@Directive({\n selector: \"ng-template[lilyTextSizePickerIcon]\",\n standalone: true,\n})\nexport class TextSizePickerIcon {\n static ngTemplateContextGuard(\n _dir: TextSizePickerIcon,\n _ctx: unknown,\n ): _ctx is ChildArgs & { $implicit: ChildArgs } {\n return true;\n }\n}\n\n/**\n * TextSizePicker — `data-text-size` text-size picker.\n *\n * Renders an icon button that opens a WAI-ARIA APG listbox of sizes. On\n * every size change the component sets `data-text-size=\"{slug}\"` on the\n * document root (or on a consumer-supplied target), with optional\n * `localStorage` persistence. Ships no CSS; the consumer maps each\n * `[data-text-size=\"…\"]` slug to real typography. See `spec/index.md`\n * for the full contract.\n */\n@Component({\n selector: \"lily-text-size-picker\",\n standalone: true,\n imports: [NgTemplateOutlet, IconButton, Listbox],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n \"(document:click)\": \"onDocumentClick($event)\",\n },\n template: `\n <div\n #rootEl\n class=\"text-size-picker {{ className() }}\"\n (focusout)=\"onRootFocusOut($event)\"\n >\n <input type=\"hidden\" [name]=\"name()\" [value]=\"value()\" />\n\n <lily-icon-button\n #buttonEl\n [label]=\"label()\"\n baseClass=\"text-size-picker-button\"\n ariaHaspopup=\"listbox\"\n [ariaExpanded]=\"open()\"\n [ariaControls]=\"listId\"\n (click)=\"toggle()\"\n (keydown)=\"onButtonKeydown($event)\"\n >\n @if (iconTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"childContext()\"\n />\n } @else {\n <svg\n class=\"text-size-picker-icon\"\n viewBox=\"0 0 16 16\"\n width=\"1.05rem\"\n height=\"1.05rem\"\n aria-hidden=\"true\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.6\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"M4 13 7.2 3h1.6L12 13M5.4 9.5h5.2\" />\n </svg>\n }\n </lily-icon-button>\n\n <lily-listbox\n #listEl\n [label]=\"label()\"\n baseClass=\"text-size-picker-list\"\n [elementId]=\"listId\"\n navigation=\"active-descendant\"\n [clamp]=\"true\"\n [typeahead]=\"true\"\n [pageSize]=\"10\"\n [(activeIndex)]=\"activeIndex\"\n [hidden]=\"!open()\"\n (activate)=\"choose($event)\"\n (escape)=\"closeList()\"\n (tabOut)=\"onListTabOut()\"\n >\n @for (size of sizes(); track size; let i = $index) {\n <li\n class=\"text-size-picker-option\"\n [id]=\"optionId(i)\"\n role=\"option\"\n [attr.aria-selected]=\"size === value()\"\n [attr.data-active]=\"i === activeIndex() ? '' : null\"\n (click)=\"choose(i)\"\n >\n {{ labelFor(size) }}\n </li>\n }\n </lily-listbox>\n </div>\n `,\n})\nexport class TextSizePicker {\n readonly label = input.required<string>();\n readonly sizes = input.required<string[]>();\n readonly value = model<string>(\"\");\n readonly defaultValue = input<string>(\"\");\n readonly storageKey = input<string>(\"\");\n readonly name = input<string>(\"text-size\");\n readonly target = input<HTMLElement | null>(null);\n readonly sizeLabels = input<Record<string, string>>({});\n readonly className = input<string>(\"\");\n readonly sizeChange = output<string>();\n\n /** Projected icon template; replaces the default icon when supplied. */\n protected readonly iconTemplate = contentChild(TemplateRef);\n\n private readonly rootRef =\n viewChild.required<ElementRef<HTMLDivElement>>(\"rootEl\");\n // Angular resolves a template-ref-variable on a component tag to the\n // component INSTANCE by default (not its ElementRef) — exactly what's\n // needed to call the headless components' own public `focus()` methods.\n private readonly buttonRef = viewChild.required<IconButton>(\"buttonEl\");\n private readonly listRef = viewChild.required<Listbox>(\"listEl\");\n\n private readonly baseId = nextTextSizePickerId();\n protected readonly listId = `${this.baseId}-list`;\n\n protected readonly open = signal(false);\n protected readonly activeIndex = signal(-1);\n\n protected readonly childContext = computed(() => {\n const args: ChildArgs = {\n value: this.value(),\n open: this.open(),\n labelFor: (size: string) => this.labelFor(size),\n };\n return { $implicit: args, ...args };\n });\n\n private initialised = false;\n\n constructor() {\n effect(() => {\n const current = this.value();\n\n if (!this.initialised) {\n this.initialised = true;\n let initial = current;\n\n const sk = this.storageKey();\n if (!initial && sk) {\n try {\n initial =\n (typeof localStorage !== \"undefined\"\n ? localStorage.getItem(sk)\n : null) ?? \"\";\n } catch {\n // ignore privacy errors\n }\n }\n\n if (!initial) {\n const sizes = this.sizes();\n const dv = this.defaultValue();\n initial =\n dv || (sizes.includes(\"medium\") ? \"medium\" : sizes[0]) || \"\";\n }\n\n if (initial && initial !== current) {\n this.value.set(initial);\n return;\n }\n }\n\n if (current) this.applySize(current);\n });\n }\n\n protected optionId(index: number): string {\n return `${this.baseId}-option-${index}`;\n }\n\n labelFor(size: string): string {\n const labels = this.sizeLabels();\n if (size in labels) return labels[size];\n return sizeName(size);\n }\n\n // ---------------------------------------------------------------\n // Open / close\n // ---------------------------------------------------------------\n\n /** Under zoneless change detection the signal write that un-hides the\n * list has not reached the DOM when a microtask runs, so focusing the\n * still-hidden list fails silently and Escape lands on the button.\n * Flushing detection first is the Angular equivalent of Svelte's\n * synchronous update (same fix the date-time-picker port recorded). */\n private readonly cdr = inject(ChangeDetectorRef);\n\n protected toggle(): void {\n if (this.open()) this.closeList();\n else this.openList();\n }\n\n /** Open the listbox, activating `startIndex` (default: the selection). */\n openList(startIndex?: number): void {\n const selected = this.sizes().indexOf(this.value());\n // An empty list has no option to activate; -1 keeps\n // aria-activedescendant off rather than pointing at an id that\n // does not exist.\n this.activeIndex.set(\n this.sizes().length === 0\n ? -1\n : (startIndex ?? (selected >= 0 ? selected : 0)),\n );\n this.open.set(true);\n // Focus moves to the listbox; the active option is conveyed via\n // aria-activedescendant, per the APG listbox pattern.\n this.cdr.detectChanges();\n queueMicrotask(() => {\n this.listRef().focus({ preventScroll: true });\n this.scrollActiveIntoView();\n });\n }\n\n /** Close the listbox; `refocus` returns focus to the button. */\n closeList(refocus = true): void {\n if (!this.open()) return;\n this.open.set(false);\n this.activeIndex.set(-1);\n if (refocus) queueMicrotask(() => this.buttonRef().focus({ preventScroll: true }));\n }\n\n protected choose(index: number): void {\n const slug = this.sizes()[index];\n if (slug) this.value.set(slug);\n this.closeList();\n }\n\n private scrollActiveIntoView(): void {\n const i = this.activeIndex();\n if (i < 0) return;\n // getElementById rather than reaching into the composed Listbox's\n // DOM: Listbox exposes behaviour (focus()) and state (activeIndex),\n // not its rendered children, the same \"consumer owns the option\n // elements\" division of responsibility the headless component\n // documents for itself.\n const el = document.getElementById(this.optionId(i));\n // jsdom does not implement scrollIntoView; call it only if present.\n el?.scrollIntoView?.({ block: \"nearest\" });\n }\n\n // Arrow/Home/End/PageUp/PageDown/typeahead/Escape/Tab keyboard handling\n // inside the open list is owned by the composed `lily-listbox`'s\n // `navigation=\"active-descendant\"` mode (see\n // @lilydesignsystem/angular-headless); this component only decides\n // what open/close/choose/scroll mean, and keeps the highlighted option\n // in view whenever activeIndex changes.\n private readonly scrollOnActiveIndexChange = effect(() => {\n this.activeIndex();\n this.scrollActiveIntoView();\n });\n\n // ---------------------------------------------------------------\n // Keyboard\n // ---------------------------------------------------------------\n\n protected onButtonKeydown(event: KeyboardEvent): void {\n switch (event.key) {\n case \"ArrowDown\":\n case \"Enter\":\n case \" \":\n event.preventDefault();\n this.openList();\n break;\n case \"ArrowUp\":\n event.preventDefault();\n this.openList(this.sizes().length - 1);\n break;\n }\n }\n\n /** `lily-listbox` Tab handling never prevents the key — focus goes to\n * the button FIRST so the browser's default Tab move computes from\n * the picker's position rather than from <body> (hiding the focused\n * list drops focus there, and the browser would otherwise compute the\n * default Tab move from the top of the document). */\n protected onListTabOut(): void {\n this.buttonRef().focus({ preventScroll: true });\n this.closeList(false);\n }\n\n protected onRootFocusOut(event: FocusEvent): void {\n const next = event.relatedTarget as Node | null;\n if (next && this.rootRef().nativeElement.contains(next)) return;\n this.closeList(false);\n }\n\n protected onDocumentClick(event: Event): void {\n if (!this.open()) return;\n const t = event.target as Node | null;\n if (t && !this.rootRef().nativeElement.contains(t)) this.closeList(false);\n }\n\n // ---------------------------------------------------------------\n // Apply\n // ---------------------------------------------------------------\n\n private applySize(slug: string): void {\n if (typeof document === \"undefined\" || !slug) return;\n (this.target() ?? document.documentElement).setAttribute(\n \"data-text-size\",\n slug,\n );\n\n const sk = this.storageKey();\n if (sk) {\n try {\n localStorage.setItem(sk, slug);\n } catch {\n // ignore quota / privacy errors\n }\n }\n this.sizeChange.emit(slug);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "name": "@lilydesignsystem/angular-text-size-picker",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "engines": {
5
5
  "node": "=26"
6
6
  },
7
7
  "description": "Lily Design System Angular 20 text size picker: an icon button opening an APG listbox that sets data-text-size on the document. Headless, SSR-safe, no CSS.",
8
+ "dependencies": {
9
+ "@lilydesignsystem/angular-headless": "^0.2.0",
10
+ "tslib": "^2.3.0"
11
+ },
8
12
  "peerDependencies": {
9
13
  "@angular/core": ">=20.0.0 <23.0.0",
10
14
  "@angular/common": ">=20.0.0 <23.0.0"
@@ -40,8 +44,5 @@
40
44
  "default": "./fesm2022/lilydesignsystem-angular-text-size-picker.mjs"
41
45
  }
42
46
  },
43
- "type": "module",
44
- "dependencies": {
45
- "tslib": "^2.3.0"
46
- }
47
+ "type": "module"
47
48
  }
@@ -78,8 +78,6 @@ declare class TextSizePicker {
78
78
  protected readonly listId: string;
79
79
  protected readonly open: _angular_core.WritableSignal<boolean>;
80
80
  protected readonly activeIndex: _angular_core.WritableSignal<number>;
81
- /** `aria-activedescendant` is only meaningful while the listbox is open. */
82
- protected readonly activeDescendant: _angular_core.Signal<string | null>;
83
81
  protected readonly childContext: _angular_core.Signal<{
84
82
  /** Currently selected size slug. */
85
83
  value: string;
@@ -89,8 +87,6 @@ declare class TextSizePicker {
89
87
  labelFor: (size: string) => string;
90
88
  $implicit: ChildArgs;
91
89
  }>;
92
- private typeahead;
93
- private typeaheadTimer;
94
90
  private initialised;
95
91
  constructor();
96
92
  protected optionId(index: number): string;
@@ -108,10 +104,14 @@ declare class TextSizePicker {
108
104
  closeList(refocus?: boolean): void;
109
105
  protected choose(index: number): void;
110
106
  private scrollActiveIntoView;
111
- private moveActive;
112
- private runTypeahead;
107
+ private readonly scrollOnActiveIndexChange;
113
108
  protected onButtonKeydown(event: KeyboardEvent): void;
114
- protected onListKeydown(event: KeyboardEvent): void;
109
+ /** `lily-listbox` Tab handling never prevents the key — focus goes to
110
+ * the button FIRST so the browser's default Tab move computes from
111
+ * the picker's position rather than from <body> (hiding the focused
112
+ * list drops focus there, and the browser would otherwise compute the
113
+ * default Tab move from the top of the document). */
114
+ protected onListTabOut(): void;
115
115
  protected onRootFocusOut(event: FocusEvent): void;
116
116
  protected onDocumentClick(event: Event): void;
117
117
  private applySize;
@@ -1 +1 @@
1
- {"version":3,"file":"lilydesignsystem-angular-text-size-picker.d.ts","sources":["../../text-size-picker.component.ts"],"mappings":";;;AAoBA;;;;;;;;;AASG;AAEH;AACM,KAAM,SAAS;;;;;;AAMnB;;AAGF;;;;AAIG;AACH,iBAAgB,QAAQ;AAQxB;AACA,iBAAgB,oBAAoB;AAKpC;;;;;;;;;;;;AAYG;AACH,cAIa,kBAAkB;AAC7B,wCACQ,kBAAkB,0BAEf,SAAS;mBAAgB,SAAS;AAAE;oDAJpC,kBAAkB;sDAAlB,kBAAkB;AAO9B;AAED;;;;;;;;;AASG;AACH,cA6Ea,cAAc;oBACX,aAAA,CAAA,WAAA;oBACA,aAAA,CAAA,WAAA;oBACA,aAAA,CAAA,WAAA;2BACO,aAAA,CAAA,WAAA;yBACF,aAAA,CAAA,WAAA;mBACN,aAAA,CAAA,WAAA;qBACE,aAAA,CAAA,WAAA,CAAA,WAAA;yBACI,aAAA,CAAA,WAAA,CAAA,MAAA;wBACD,aAAA,CAAA,WAAA;yBACC,aAAA,CAAA,gBAAA;;AAGnB,qCAA+B,aAAA,CAAA,MAAA,CAAA,WAAA;AAE/B;AAEA;AAEA;AAGA;AACA;AAEA,6BAAuB,aAAA,CAAA,cAAA;AACvB,oCAA8B,aAAA,CAAA,cAAA;;AAG9B,yCAAmC,aAAA,CAAA,MAAA;qCAKJ,aAAA,CAAA,MAAA;;;;;;AAzKrB;;AAgLP;;;;;AA+CH;AAIA;AAUA;;;;AAIuE;AACvE;;;AAQA;;AAqBA;AAOA;AAMA;AASA;AAUA;AA+BA,qCAAiC,aAAa;AAe9C,mCAA+B,aAAa;AA+D5C,oCAAgC,UAAU;AAM1C,qCAAiC,KAAK;AAUtC;oDArSW,cAAc;sDAAd,cAAc;AAsT1B;;","names":[]}
1
+ {"version":3,"file":"lilydesignsystem-angular-text-size-picker.d.ts","sources":["../../text-size-picker.component.ts"],"mappings":";;;AAoBA;;;;;;;;;AASG;AAEH;AACM,KAAM,SAAS;;;;;;AAMnB;;AAGF;;;;AAIG;AACH,iBAAgB,QAAQ;AAQxB;AACA,iBAAgB,oBAAoB;AAKpC;;;;;;;;;;;;AAYG;AACH,cAIa,kBAAkB;AAC7B,wCACQ,kBAAkB,0BAEf,SAAS;mBAAgB,SAAS;AAAE;oDAJpC,kBAAkB;sDAAlB,kBAAkB;AAO9B;AAED;;;;;;;;;AASG;AACH,cAgFa,cAAc;oBACX,aAAA,CAAA,WAAA;oBACA,aAAA,CAAA,WAAA;oBACA,aAAA,CAAA,WAAA;2BACO,aAAA,CAAA,WAAA;yBACF,aAAA,CAAA,WAAA;mBACN,aAAA,CAAA,WAAA;qBACE,aAAA,CAAA,WAAA,CAAA,WAAA;yBACI,aAAA,CAAA,WAAA,CAAA,MAAA;wBACD,aAAA,CAAA,WAAA;yBACC,aAAA,CAAA,gBAAA;;AAGnB,qCAA+B,aAAA,CAAA,MAAA,CAAA,WAAA;AAE/B;AAKA;AACA;AAEA;AACA;AAEA,6BAAuB,aAAA,CAAA,cAAA;AACvB,oCAA8B,aAAA,CAAA,cAAA;qCAEC,aAAA,CAAA,MAAA;;;;;;AAvKrB;;AA8KP;;;AAyCH;AAIA;AAUA;;;;AAIuE;AACvE;;;AAQA;;AAqBA;AAOA;AAMA;AAmBA;AASA,qCAAiC,aAAa;AAe9C;;;;AAIqD;;AAMrD,oCAAgC,UAAU;AAM1C,qCAAiC,KAAK;AAUtC;oDA/MW,cAAc;sDAAd,cAAc;AAgO1B;;","names":[]}