@lilydesignsystem/angular-motion-picker 0.1.0

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.
@@ -0,0 +1,536 @@
1
+ import { NgTemplateOutlet } from '@angular/common';
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';
4
+
5
+ /**
6
+ * Resolve a motion slug to its display label: each hyphen-separated
7
+ * word title-cased, so a slug like "no-preference" renders as
8
+ * "No Preference". Mirrors `sizeName` in text-size-picker and
9
+ * `themeName` in theme-picker.
10
+ */
11
+ function motionName(motion) {
12
+ return motion
13
+ .split("-")
14
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
15
+ .join(" ");
16
+ }
17
+ /**
18
+ * True when the platform reports a preference for reduced motion.
19
+ * SSR-safe: `window`/`matchMedia` are absent on the server, so this
20
+ * resolves to `false` there and the client re-derives it on mount.
21
+ */
22
+ function prefersReducedMotion() {
23
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
24
+ return false;
25
+ }
26
+ try {
27
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ }
33
+ let uid = 0;
34
+ /** Stable per-instance id prefix; SSR-safe (no Math.random / Date.now). */
35
+ function nextMotionPickerId() {
36
+ uid += 1;
37
+ return `motion-picker-${uid}`;
38
+ }
39
+ /**
40
+ * Optional marker for the projected icon template. Gives consumers typed
41
+ * `let-` variables:
42
+ *
43
+ * ```html
44
+ * <lily-motion-picker ...>
45
+ * <ng-template lilyMotionPickerIcon let-args>{{ args.labelFor(args.value) }}</ng-template>
46
+ * </lily-motion-picker>
47
+ * ```
48
+ *
49
+ * The component queries any projected `<ng-template>`, so the marker is
50
+ * for type-checking and readability, not for matching.
51
+ */
52
+ class MotionPickerIcon {
53
+ static ngTemplateContextGuard(_dir, _ctx) {
54
+ return true;
55
+ }
56
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: MotionPickerIcon, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
57
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.4", type: MotionPickerIcon, isStandalone: true, selector: "ng-template[lilyMotionPickerIcon]", ngImport: i0 }); }
58
+ }
59
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: MotionPickerIcon, decorators: [{
60
+ type: Directive,
61
+ args: [{
62
+ selector: "ng-template[lilyMotionPickerIcon]",
63
+ standalone: true,
64
+ }]
65
+ }] });
66
+ /**
67
+ * MotionPicker — `data-motion` reduced-motion picker.
68
+ *
69
+ * Renders an icon button that opens a WAI-ARIA APG listbox of motion
70
+ * slugs. On every change the component sets `data-motion="{slug}"` on
71
+ * the document root (or on a consumer-supplied target), with optional
72
+ * `localStorage` persistence. Its initial value defers to the
73
+ * platform's `(prefers-reduced-motion: reduce)` media query before
74
+ * falling back to a fixed default — the one behaviour difference from
75
+ * its `theme-picker`/`text-size-picker` siblings. Ships no CSS; the
76
+ * consumer decides what `[data-motion="reduce"]` actually suppresses.
77
+ * See `spec/index.md` for the full contract.
78
+ */
79
+ class MotionPicker {
80
+ constructor() {
81
+ this.label = input.required(/* @ts-ignore */
82
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
83
+ this.motions = input.required(/* @ts-ignore */
84
+ ...(ngDevMode ? [{ debugName: "motions" }] : /* istanbul ignore next */ []));
85
+ this.value = model("", /* @ts-ignore */
86
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
87
+ this.defaultValue = input("", /* @ts-ignore */
88
+ ...(ngDevMode ? [{ debugName: "defaultValue" }] : /* istanbul ignore next */ []));
89
+ this.storageKey = input("", /* @ts-ignore */
90
+ ...(ngDevMode ? [{ debugName: "storageKey" }] : /* istanbul ignore next */ []));
91
+ this.name = input("motion", /* @ts-ignore */
92
+ ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
93
+ this.target = input(null, /* @ts-ignore */
94
+ ...(ngDevMode ? [{ debugName: "target" }] : /* istanbul ignore next */ []));
95
+ this.motionLabels = input({}, /* @ts-ignore */
96
+ ...(ngDevMode ? [{ debugName: "motionLabels" }] : /* istanbul ignore next */ []));
97
+ this.className = input("", /* @ts-ignore */
98
+ ...(ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []));
99
+ this.motionChange = output();
100
+ /** Projected icon template; replaces the default icon when supplied. */
101
+ this.iconTemplate = contentChild(TemplateRef, /* @ts-ignore */
102
+ ...(ngDevMode ? [{ debugName: "iconTemplate" }] : /* istanbul ignore next */ []));
103
+ this.rootRef = viewChild.required("rootEl", /* @ts-ignore */
104
+ ...(ngDevMode ? [{ debugName: "rootRef" }] : /* istanbul ignore next */ []));
105
+ this.buttonRef = viewChild.required("buttonEl", /* @ts-ignore */
106
+ ...(ngDevMode ? [{ debugName: "buttonRef" }] : /* istanbul ignore next */ []));
107
+ this.listRef = viewChild.required("listEl", /* @ts-ignore */
108
+ ...(ngDevMode ? [{ debugName: "listRef" }] : /* istanbul ignore next */ []));
109
+ this.baseId = nextMotionPickerId();
110
+ this.listId = `${this.baseId}-list`;
111
+ this.open = signal(false, /* @ts-ignore */
112
+ ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
113
+ this.activeIndex = signal(-1, /* @ts-ignore */
114
+ ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
115
+ /** `aria-activedescendant` is only meaningful while the listbox is open. */
116
+ this.activeDescendant = computed(() => {
117
+ const i = this.activeIndex();
118
+ return this.open() && i >= 0 ? this.optionId(i) : null;
119
+ }, /* @ts-ignore */
120
+ ...(ngDevMode ? [{ debugName: "activeDescendant" }] : /* istanbul ignore next */ []));
121
+ this.childContext = computed(() => {
122
+ const args = {
123
+ value: this.value(),
124
+ open: this.open(),
125
+ labelFor: (motion) => this.labelFor(motion),
126
+ };
127
+ return { $implicit: args, ...args };
128
+ }, /* @ts-ignore */
129
+ ...(ngDevMode ? [{ debugName: "childContext" }] : /* istanbul ignore next */ []));
130
+ // Typeahead buffer: APG listbox behaviour. Reset after a pause.
131
+ this.typeahead = "";
132
+ this.initialised = false;
133
+ // ---------------------------------------------------------------
134
+ // Open / close
135
+ // ---------------------------------------------------------------
136
+ /** Under zoneless change detection the signal write that un-hides the
137
+ * list has not reached the DOM when a microtask runs, so focusing the
138
+ * still-hidden list fails silently and Escape lands on the button.
139
+ * Flushing detection first is the Angular equivalent of Svelte's
140
+ * synchronous update (same fix the date-time-picker port recorded). */
141
+ this.cdr = inject(ChangeDetectorRef);
142
+ inject(DestroyRef).onDestroy(() => clearTimeout(this.typeaheadTimer));
143
+ effect(() => {
144
+ const current = this.value();
145
+ if (!this.initialised) {
146
+ this.initialised = true;
147
+ let initial = current;
148
+ const sk = this.storageKey();
149
+ if (!initial && sk) {
150
+ try {
151
+ initial =
152
+ (typeof localStorage !== "undefined"
153
+ ? localStorage.getItem(sk)
154
+ : null) ?? "";
155
+ }
156
+ catch {
157
+ // ignore privacy errors
158
+ }
159
+ }
160
+ if (!initial) {
161
+ // Unlike text-size-picker's "medium" default, motion has a
162
+ // real external signal to defer to: the platform's own
163
+ // (prefers-reduced-motion: reduce) media query.
164
+ const motions = this.motions();
165
+ const dv = this.defaultValue();
166
+ const osPreferred = prefersReducedMotion() ? "reduce" : "no-preference";
167
+ initial =
168
+ dv ||
169
+ (motions.includes(osPreferred) ? osPreferred : undefined) ||
170
+ motions[0] ||
171
+ "";
172
+ }
173
+ if (initial && initial !== current) {
174
+ this.value.set(initial);
175
+ return;
176
+ }
177
+ }
178
+ if (current)
179
+ this.applyMotion(current);
180
+ });
181
+ }
182
+ optionId(index) {
183
+ return `${this.baseId}-option-${index}`;
184
+ }
185
+ labelFor(motion) {
186
+ const labels = this.motionLabels();
187
+ if (motion in labels)
188
+ return labels[motion];
189
+ return motionName(motion);
190
+ }
191
+ toggle() {
192
+ if (this.open())
193
+ this.closeList();
194
+ else
195
+ this.openList();
196
+ }
197
+ /** Open the listbox, activating `startIndex` (default: the selection). */
198
+ openList(startIndex) {
199
+ const selected = this.motions().indexOf(this.value());
200
+ // An empty list has no option to activate; -1 keeps
201
+ // aria-activedescendant off rather than pointing at an id that
202
+ // does not exist.
203
+ this.activeIndex.set(this.motions().length === 0
204
+ ? -1
205
+ : (startIndex ?? (selected >= 0 ? selected : 0)));
206
+ this.open.set(true);
207
+ // Focus moves to the listbox; the active option is conveyed via
208
+ // aria-activedescendant, per the APG listbox pattern.
209
+ this.cdr.detectChanges();
210
+ queueMicrotask(() => {
211
+ this.listRef().nativeElement.focus({ preventScroll: true });
212
+ this.scrollActiveIntoView();
213
+ });
214
+ }
215
+ /** Close the listbox; `refocus` returns focus to the button. */
216
+ closeList(refocus = true) {
217
+ if (!this.open())
218
+ return;
219
+ this.open.set(false);
220
+ this.activeIndex.set(-1);
221
+ if (refocus)
222
+ queueMicrotask(() => this.buttonRef().nativeElement.focus({ preventScroll: true }));
223
+ }
224
+ choose(index) {
225
+ const slug = this.motions()[index];
226
+ if (slug)
227
+ this.value.set(slug);
228
+ this.closeList();
229
+ }
230
+ scrollActiveIntoView() {
231
+ const i = this.activeIndex();
232
+ if (i < 0)
233
+ return;
234
+ const el = this.listRef().nativeElement.children[i];
235
+ // jsdom does not implement scrollIntoView; call it only if present.
236
+ el?.scrollIntoView?.({ block: "nearest" });
237
+ }
238
+ moveActive(delta) {
239
+ const count = this.motions().length;
240
+ if (count === 0)
241
+ return;
242
+ // Clamp rather than wrap, per the APG listbox pattern.
243
+ this.activeIndex.set(Math.min(Math.max(this.activeIndex() + delta, 0), count - 1));
244
+ this.scrollActiveIntoView();
245
+ }
246
+ runTypeahead(char) {
247
+ const lower = char.toLowerCase();
248
+ // APG listbox typeahead: a single character moves to the NEXT
249
+ // option starting with it, and repeating that character keeps
250
+ // cycling. Only a buffer of differing characters refines the
251
+ // match, and that buffer stays anchored on the active option.
252
+ const sameCharRun = this.typeahead === "" || [...this.typeahead].every((c) => c === lower);
253
+ this.typeahead += lower;
254
+ clearTimeout(this.typeaheadTimer);
255
+ this.typeaheadTimer = setTimeout(() => (this.typeahead = ""), 500);
256
+ const query = sameCharRun ? lower : this.typeahead;
257
+ const motions = this.motions();
258
+ const anchor = this.activeIndex() < 0 ? 0 : this.activeIndex();
259
+ const start = sameCharRun ? anchor + 1 : anchor;
260
+ // Search forward, wrapping once — typeahead wraps even though the
261
+ // arrows clamp, or options above the cursor would be untypable.
262
+ for (let n = 0; n < motions.length; n++) {
263
+ const i = (start + n) % motions.length;
264
+ if (this.labelFor(motions[i]).toLowerCase().startsWith(query)) {
265
+ this.activeIndex.set(i);
266
+ this.scrollActiveIntoView();
267
+ return;
268
+ }
269
+ }
270
+ }
271
+ // ---------------------------------------------------------------
272
+ // Keyboard
273
+ // ---------------------------------------------------------------
274
+ onButtonKeydown(event) {
275
+ switch (event.key) {
276
+ case "ArrowDown":
277
+ case "Enter":
278
+ case " ":
279
+ event.preventDefault();
280
+ this.openList();
281
+ break;
282
+ case "ArrowUp":
283
+ event.preventDefault();
284
+ this.openList(this.motions().length - 1);
285
+ break;
286
+ }
287
+ }
288
+ onListKeydown(event) {
289
+ switch (event.key) {
290
+ case "ArrowDown":
291
+ event.preventDefault();
292
+ this.moveActive(1);
293
+ break;
294
+ case "ArrowUp":
295
+ event.preventDefault();
296
+ this.moveActive(-1);
297
+ break;
298
+ case "Home":
299
+ event.preventDefault();
300
+ this.activeIndex.set(0);
301
+ this.scrollActiveIntoView();
302
+ break;
303
+ case "End":
304
+ event.preventDefault();
305
+ this.activeIndex.set(this.motions().length - 1);
306
+ this.scrollActiveIntoView();
307
+ break;
308
+ case "Enter":
309
+ case " ":
310
+ event.preventDefault();
311
+ if (this.activeIndex() >= 0)
312
+ this.choose(this.activeIndex());
313
+ break;
314
+ case "Escape":
315
+ event.preventDefault();
316
+ this.closeList();
317
+ break;
318
+ case "PageUp":
319
+ event.preventDefault();
320
+ this.moveActive(-10);
321
+ break;
322
+ case "PageDown":
323
+ // ±10, clamped: an APG-optional key for long motion lists.
324
+ event.preventDefault();
325
+ this.moveActive(10);
326
+ break;
327
+ case "Tab":
328
+ // Tab moves on — but focus goes to the button FIRST, without
329
+ // cancelling the key. Hiding the focused list drops focus to
330
+ // <body>, and the browser then computes the default Tab move
331
+ // from the top of the document, so tabbing out of an open
332
+ // picker teleported the user to the page's first tab stop.
333
+ // From the button, the default Tab lands exactly where leaving
334
+ // the picker should. The button always exists, so no
335
+ // detectChanges is needed before the focus move; guard the
336
+ // METHOD because jsdom-shaped hosts may not implement it.
337
+ this.buttonRef().nativeElement.focus?.({ preventScroll: true });
338
+ this.closeList(false);
339
+ break;
340
+ default:
341
+ if (event.key.length === 1 &&
342
+ !event.ctrlKey &&
343
+ !event.metaKey &&
344
+ !event.altKey) {
345
+ this.runTypeahead(event.key);
346
+ }
347
+ }
348
+ }
349
+ onRootFocusOut(event) {
350
+ const next = event.relatedTarget;
351
+ if (next && this.rootRef().nativeElement.contains(next))
352
+ return;
353
+ this.closeList(false);
354
+ }
355
+ onDocumentClick(event) {
356
+ if (!this.open())
357
+ return;
358
+ const t = event.target;
359
+ if (t && !this.rootRef().nativeElement.contains(t))
360
+ this.closeList(false);
361
+ }
362
+ // ---------------------------------------------------------------
363
+ // Apply
364
+ // ---------------------------------------------------------------
365
+ applyMotion(slug) {
366
+ if (typeof document === "undefined" || !slug)
367
+ return;
368
+ (this.target() ?? document.documentElement).setAttribute("data-motion", slug);
369
+ const sk = this.storageKey();
370
+ if (sk) {
371
+ try {
372
+ localStorage.setItem(sk, slug);
373
+ }
374
+ catch {
375
+ // ignore quota / privacy errors
376
+ }
377
+ }
378
+ this.motionChange.emit(slug);
379
+ }
380
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: MotionPicker, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
381
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.4", type: MotionPicker, isStandalone: true, selector: "lily-motion-picker", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, motions: { classPropertyName: "motions", publicName: "motions", 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 }, motionLabels: { classPropertyName: "motionLabels", publicName: "motionLabels", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", motionChange: "motionChange" }, 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: `
382
+ <div
383
+ #rootEl
384
+ class="motion-picker {{ className() }}"
385
+ (focusout)="onRootFocusOut($event)"
386
+ >
387
+ <input type="hidden" [name]="name()" [value]="value()" />
388
+
389
+ <button
390
+ #buttonEl
391
+ type="button"
392
+ class="motion-picker-button"
393
+ [attr.aria-label]="label() || null"
394
+ aria-haspopup="listbox"
395
+ [attr.aria-expanded]="open()"
396
+ [attr.aria-controls]="listId"
397
+ (click)="toggle()"
398
+ (keydown)="onButtonKeydown($event)"
399
+ >
400
+ @if (iconTemplate(); as tpl) {
401
+ <ng-container
402
+ [ngTemplateOutlet]="tpl"
403
+ [ngTemplateOutletContext]="childContext()"
404
+ />
405
+ } @else {
406
+ <svg
407
+ class="motion-picker-icon"
408
+ viewBox="0 0 16 16"
409
+ width="1.05rem"
410
+ height="1.05rem"
411
+ aria-hidden="true"
412
+ fill="none"
413
+ stroke="currentColor"
414
+ stroke-width="1.6"
415
+ stroke-linecap="round"
416
+ stroke-linejoin="round"
417
+ >
418
+ <path d="M5 3v10M11 3v10" />
419
+ </svg>
420
+ }
421
+ </button>
422
+
423
+ <ul
424
+ #listEl
425
+ class="motion-picker-list"
426
+ [id]="listId"
427
+ role="listbox"
428
+ [attr.aria-label]="label() || null"
429
+ [attr.aria-activedescendant]="activeDescendant()"
430
+ tabindex="-1"
431
+ [attr.hidden]="open() ? null : ''"
432
+ (keydown)="onListKeydown($event)"
433
+ >
434
+ @for (motion of motions(); track motion; let i = $index) {
435
+ <li
436
+ class="motion-picker-option"
437
+ [id]="optionId(i)"
438
+ role="option"
439
+ [attr.aria-selected]="motion === value()"
440
+ [attr.data-active]="i === activeIndex() ? '' : null"
441
+ (click)="choose(i)"
442
+ >
443
+ {{ labelFor(motion) }}
444
+ </li>
445
+ }
446
+ </ul>
447
+ </div>
448
+ `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
449
+ }
450
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.4", ngImport: i0, type: MotionPicker, decorators: [{
451
+ type: Component,
452
+ args: [{
453
+ selector: "lily-motion-picker",
454
+ standalone: true,
455
+ imports: [NgTemplateOutlet],
456
+ changeDetection: ChangeDetectionStrategy.OnPush,
457
+ host: {
458
+ "(document:click)": "onDocumentClick($event)",
459
+ },
460
+ template: `
461
+ <div
462
+ #rootEl
463
+ class="motion-picker {{ className() }}"
464
+ (focusout)="onRootFocusOut($event)"
465
+ >
466
+ <input type="hidden" [name]="name()" [value]="value()" />
467
+
468
+ <button
469
+ #buttonEl
470
+ type="button"
471
+ class="motion-picker-button"
472
+ [attr.aria-label]="label() || null"
473
+ aria-haspopup="listbox"
474
+ [attr.aria-expanded]="open()"
475
+ [attr.aria-controls]="listId"
476
+ (click)="toggle()"
477
+ (keydown)="onButtonKeydown($event)"
478
+ >
479
+ @if (iconTemplate(); as tpl) {
480
+ <ng-container
481
+ [ngTemplateOutlet]="tpl"
482
+ [ngTemplateOutletContext]="childContext()"
483
+ />
484
+ } @else {
485
+ <svg
486
+ class="motion-picker-icon"
487
+ viewBox="0 0 16 16"
488
+ width="1.05rem"
489
+ height="1.05rem"
490
+ aria-hidden="true"
491
+ fill="none"
492
+ stroke="currentColor"
493
+ stroke-width="1.6"
494
+ stroke-linecap="round"
495
+ stroke-linejoin="round"
496
+ >
497
+ <path d="M5 3v10M11 3v10" />
498
+ </svg>
499
+ }
500
+ </button>
501
+
502
+ <ul
503
+ #listEl
504
+ class="motion-picker-list"
505
+ [id]="listId"
506
+ role="listbox"
507
+ [attr.aria-label]="label() || null"
508
+ [attr.aria-activedescendant]="activeDescendant()"
509
+ tabindex="-1"
510
+ [attr.hidden]="open() ? null : ''"
511
+ (keydown)="onListKeydown($event)"
512
+ >
513
+ @for (motion of motions(); track motion; let i = $index) {
514
+ <li
515
+ class="motion-picker-option"
516
+ [id]="optionId(i)"
517
+ role="option"
518
+ [attr.aria-selected]="motion === value()"
519
+ [attr.data-active]="i === activeIndex() ? '' : null"
520
+ (click)="choose(i)"
521
+ >
522
+ {{ labelFor(motion) }}
523
+ </li>
524
+ }
525
+ </ul>
526
+ </div>
527
+ `,
528
+ }]
529
+ }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], motions: [{ type: i0.Input, args: [{ isSignal: true, alias: "motions", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], defaultValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValue", required: false }] }], storageKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "storageKey", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], motionLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "motionLabels", required: false }] }], className: [{ type: i0.Input, args: [{ isSignal: true, alias: "className", required: false }] }], motionChange: [{ type: i0.Output, args: ["motionChange"] }], iconTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TemplateRef), { isSignal: true }] }], rootRef: [{ type: i0.ViewChild, args: ["rootEl", { isSignal: true }] }], buttonRef: [{ type: i0.ViewChild, args: ["buttonEl", { isSignal: true }] }], listRef: [{ type: i0.ViewChild, args: ["listEl", { isSignal: true }] }] } });
530
+
531
+ /**
532
+ * Generated bundle index. Do not edit.
533
+ */
534
+
535
+ export { MotionPicker, MotionPickerIcon, motionName, nextMotionPickerId, prefersReducedMotion };
536
+ //# sourceMappingURL=lilydesignsystem-angular-motion-picker.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lilydesignsystem-angular-motion-picker.mjs","sources":["../../motion-picker.component.ts","../../lilydesignsystem-angular-motion-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 (two pause bars), not a Unicode\n * character. Reversed 2026-09-16 from the font-dependent-glyph\n * convention (was U+23F8 PAUSE SIGN + U+FE0E, exported as `PAUSE_SIGN`\n * — removed, not renamed). \"Stop the moving parts\" still reads\n * directly from two bars; a bundled outline SVG matches the other four\n * picker icons as one consistent visual family regardless of the\n * consumer's fonts.\n */\n\n/** Context passed to a custom icon `<ng-template>` (the button icon). */\nexport type ChildArgs = {\n /** Currently selected motion slug. */\n value: string;\n /** Is the listbox open? */\n open: boolean;\n /** Resolve a slug to its display label. */\n labelFor: (motion: string) => string;\n};\n\n/**\n * Resolve a motion slug to its display label: each hyphen-separated\n * word title-cased, so a slug like \"no-preference\" renders as\n * \"No Preference\". Mirrors `sizeName` in text-size-picker and\n * `themeName` in theme-picker.\n */\nexport function motionName(motion: string): string {\n return motion\n .split(\"-\")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\" \");\n}\n\n/**\n * True when the platform reports a preference for reduced motion.\n * SSR-safe: `window`/`matchMedia` are absent on the server, so this\n * resolves to `false` there and the client re-derives it on mount.\n */\nexport function prefersReducedMotion(): boolean {\n if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\") {\n return false;\n }\n try {\n return window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n } catch {\n return false;\n }\n}\n\nlet uid = 0;\n/** Stable per-instance id prefix; SSR-safe (no Math.random / Date.now). */\nexport function nextMotionPickerId(): string {\n uid += 1;\n return `motion-picker-${uid}`;\n}\n\n/**\n * Optional marker for the projected icon template. Gives consumers typed\n * `let-` variables:\n *\n * ```html\n * <lily-motion-picker ...>\n * <ng-template lilyMotionPickerIcon let-args>{{ args.labelFor(args.value) }}</ng-template>\n * </lily-motion-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[lilyMotionPickerIcon]\",\n standalone: true,\n})\nexport class MotionPickerIcon {\n static ngTemplateContextGuard(\n _dir: MotionPickerIcon,\n _ctx: unknown,\n ): _ctx is ChildArgs & { $implicit: ChildArgs } {\n return true;\n }\n}\n\n/**\n * MotionPicker — `data-motion` reduced-motion picker.\n *\n * Renders an icon button that opens a WAI-ARIA APG listbox of motion\n * slugs. On every change the component sets `data-motion=\"{slug}\"` on\n * the document root (or on a consumer-supplied target), with optional\n * `localStorage` persistence. Its initial value defers to the\n * platform's `(prefers-reduced-motion: reduce)` media query before\n * falling back to a fixed default — the one behaviour difference from\n * its `theme-picker`/`text-size-picker` siblings. Ships no CSS; the\n * consumer decides what `[data-motion=\"reduce\"]` actually suppresses.\n * See `spec/index.md` for the full contract.\n */\n@Component({\n selector: \"lily-motion-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=\"motion-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=\"motion-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=\"motion-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=\"M5 3v10M11 3v10\" />\n </svg>\n }\n </button>\n\n <ul\n #listEl\n class=\"motion-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 (motion of motions(); track motion; let i = $index) {\n <li\n class=\"motion-picker-option\"\n [id]=\"optionId(i)\"\n role=\"option\"\n [attr.aria-selected]=\"motion === value()\"\n [attr.data-active]=\"i === activeIndex() ? '' : null\"\n (click)=\"choose(i)\"\n >\n {{ labelFor(motion) }}\n </li>\n }\n </ul>\n </div>\n `,\n})\nexport class MotionPicker {\n readonly label = input.required<string>();\n readonly motions = input.required<string[]>();\n readonly value = model<string>(\"\");\n readonly defaultValue = input<string>(\"\");\n readonly storageKey = input<string>(\"\");\n readonly name = input<string>(\"motion\");\n readonly target = input<HTMLElement | null>(null);\n readonly motionLabels = input<Record<string, string>>({});\n readonly className = input<string>(\"\");\n readonly motionChange = 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 = nextMotionPickerId();\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: (motion: string) => this.labelFor(motion),\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 // Unlike text-size-picker's \"medium\" default, motion has a\n // real external signal to defer to: the platform's own\n // (prefers-reduced-motion: reduce) media query.\n const motions = this.motions();\n const dv = this.defaultValue();\n const osPreferred = prefersReducedMotion() ? \"reduce\" : \"no-preference\";\n initial =\n dv ||\n (motions.includes(osPreferred) ? osPreferred : undefined) ||\n motions[0] ||\n \"\";\n }\n\n if (initial && initial !== current) {\n this.value.set(initial);\n return;\n }\n }\n\n if (current) this.applyMotion(current);\n });\n }\n\n protected optionId(index: number): string {\n return `${this.baseId}-option-${index}`;\n }\n\n labelFor(motion: string): string {\n const labels = this.motionLabels();\n if (motion in labels) return labels[motion];\n return motionName(motion);\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.motions().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.motions().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.motions()[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.motions().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 motions = this.motions();\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 < motions.length; n++) {\n const i = (start + n) % motions.length;\n if (this.labelFor(motions[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.motions().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.motions().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 motion 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 applyMotion(slug: string): void {\n if (typeof document === \"undefined\" || !slug) return;\n (this.target() ?? document.documentElement).setAttribute(\n \"data-motion\",\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.motionChange.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 ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@lilydesignsystem/angular-motion-picker",
3
+ "version": "0.1.0",
4
+ "engines": {
5
+ "node": "=26"
6
+ },
7
+ "description": "Lily Design System Angular 20 motion (reduced-motion) picker: an icon button opening an APG listbox that sets data-motion on the document, defaulting to the OS's own prefers-reduced-motion signal. Headless, SSR-safe, no CSS.",
8
+ "peerDependencies": {
9
+ "@angular/core": ">=20.0.0 <23.0.0",
10
+ "@angular/common": ">=20.0.0 <23.0.0"
11
+ },
12
+ "sideEffects": false,
13
+ "keywords": [
14
+ "lily",
15
+ "design",
16
+ "system",
17
+ "angular",
18
+ "motion",
19
+ "reduced-motion",
20
+ "picker"
21
+ ],
22
+ "author": "Joel Parker Henderson <joel@joelparkerhenderson.com>",
23
+ "license": "MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/LilyDesignSystem/lily-design-system-angular-helpers.git",
27
+ "directory": "lily-design-system-angular-motion-picker"
28
+ },
29
+ "homepage": "https://lilydesignsystem.com/",
30
+ "bugs": {
31
+ "url": "https://github.com/LilyDesignSystem/lily-design-system/issues"
32
+ },
33
+ "module": "fesm2022/lilydesignsystem-angular-motion-picker.mjs",
34
+ "typings": "types/lilydesignsystem-angular-motion-picker.d.ts",
35
+ "exports": {
36
+ "./package.json": {
37
+ "default": "./package.json"
38
+ },
39
+ ".": {
40
+ "types": "./types/lilydesignsystem-angular-motion-picker.d.ts",
41
+ "default": "./fesm2022/lilydesignsystem-angular-motion-picker.mjs"
42
+ }
43
+ },
44
+ "type": "module",
45
+ "dependencies": {
46
+ "tslib": "^2.3.0"
47
+ }
48
+ }
@@ -0,0 +1,133 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { TemplateRef } from '@angular/core';
3
+
4
+ /**
5
+ * Default button icon: a bundled SVG (two pause bars), not a Unicode
6
+ * character. Reversed 2026-09-16 from the font-dependent-glyph
7
+ * convention (was U+23F8 PAUSE SIGN + U+FE0E, exported as `PAUSE_SIGN`
8
+ * — removed, not renamed). "Stop the moving parts" still reads
9
+ * directly from two bars; a bundled outline SVG matches the other four
10
+ * picker icons as one consistent visual family regardless of the
11
+ * consumer's fonts.
12
+ */
13
+ /** Context passed to a custom icon `<ng-template>` (the button icon). */
14
+ type ChildArgs = {
15
+ /** Currently selected motion slug. */
16
+ value: string;
17
+ /** Is the listbox open? */
18
+ open: boolean;
19
+ /** Resolve a slug to its display label. */
20
+ labelFor: (motion: string) => string;
21
+ };
22
+ /**
23
+ * Resolve a motion slug to its display label: each hyphen-separated
24
+ * word title-cased, so a slug like "no-preference" renders as
25
+ * "No Preference". Mirrors `sizeName` in text-size-picker and
26
+ * `themeName` in theme-picker.
27
+ */
28
+ declare function motionName(motion: string): string;
29
+ /**
30
+ * True when the platform reports a preference for reduced motion.
31
+ * SSR-safe: `window`/`matchMedia` are absent on the server, so this
32
+ * resolves to `false` there and the client re-derives it on mount.
33
+ */
34
+ declare function prefersReducedMotion(): boolean;
35
+ /** Stable per-instance id prefix; SSR-safe (no Math.random / Date.now). */
36
+ declare function nextMotionPickerId(): string;
37
+ /**
38
+ * Optional marker for the projected icon template. Gives consumers typed
39
+ * `let-` variables:
40
+ *
41
+ * ```html
42
+ * <lily-motion-picker ...>
43
+ * <ng-template lilyMotionPickerIcon let-args>{{ args.labelFor(args.value) }}</ng-template>
44
+ * </lily-motion-picker>
45
+ * ```
46
+ *
47
+ * The component queries any projected `<ng-template>`, so the marker is
48
+ * for type-checking and readability, not for matching.
49
+ */
50
+ declare class MotionPickerIcon {
51
+ static ngTemplateContextGuard(_dir: MotionPickerIcon, _ctx: unknown): _ctx is ChildArgs & {
52
+ $implicit: ChildArgs;
53
+ };
54
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MotionPickerIcon, never>;
55
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<MotionPickerIcon, "ng-template[lilyMotionPickerIcon]", never, {}, {}, never, never, true, never>;
56
+ }
57
+ /**
58
+ * MotionPicker — `data-motion` reduced-motion picker.
59
+ *
60
+ * Renders an icon button that opens a WAI-ARIA APG listbox of motion
61
+ * slugs. On every change the component sets `data-motion="{slug}"` on
62
+ * the document root (or on a consumer-supplied target), with optional
63
+ * `localStorage` persistence. Its initial value defers to the
64
+ * platform's `(prefers-reduced-motion: reduce)` media query before
65
+ * falling back to a fixed default — the one behaviour difference from
66
+ * its `theme-picker`/`text-size-picker` siblings. Ships no CSS; the
67
+ * consumer decides what `[data-motion="reduce"]` actually suppresses.
68
+ * See `spec/index.md` for the full contract.
69
+ */
70
+ declare class MotionPicker {
71
+ readonly label: _angular_core.InputSignal<string>;
72
+ readonly motions: _angular_core.InputSignal<string[]>;
73
+ readonly value: _angular_core.ModelSignal<string>;
74
+ readonly defaultValue: _angular_core.InputSignal<string>;
75
+ readonly storageKey: _angular_core.InputSignal<string>;
76
+ readonly name: _angular_core.InputSignal<string>;
77
+ readonly target: _angular_core.InputSignal<HTMLElement | null>;
78
+ readonly motionLabels: _angular_core.InputSignal<Record<string, string>>;
79
+ readonly className: _angular_core.InputSignal<string>;
80
+ readonly motionChange: _angular_core.OutputEmitterRef<string>;
81
+ /** Projected icon template; replaces the default icon when supplied. */
82
+ protected readonly iconTemplate: _angular_core.Signal<TemplateRef<any> | undefined>;
83
+ private readonly rootRef;
84
+ private readonly buttonRef;
85
+ private readonly listRef;
86
+ private readonly baseId;
87
+ protected readonly listId: string;
88
+ protected readonly open: _angular_core.WritableSignal<boolean>;
89
+ protected readonly activeIndex: _angular_core.WritableSignal<number>;
90
+ /** `aria-activedescendant` is only meaningful while the listbox is open. */
91
+ protected readonly activeDescendant: _angular_core.Signal<string | null>;
92
+ protected readonly childContext: _angular_core.Signal<{
93
+ /** Currently selected motion slug. */
94
+ value: string;
95
+ /** Is the listbox open? */
96
+ open: boolean;
97
+ /** Resolve a slug to its display label. */
98
+ labelFor: (motion: string) => string;
99
+ $implicit: ChildArgs;
100
+ }>;
101
+ private typeahead;
102
+ private typeaheadTimer;
103
+ private initialised;
104
+ constructor();
105
+ protected optionId(index: number): string;
106
+ labelFor(motion: string): string;
107
+ /** Under zoneless change detection the signal write that un-hides the
108
+ * list has not reached the DOM when a microtask runs, so focusing the
109
+ * still-hidden list fails silently and Escape lands on the button.
110
+ * Flushing detection first is the Angular equivalent of Svelte's
111
+ * synchronous update (same fix the date-time-picker port recorded). */
112
+ private readonly cdr;
113
+ protected toggle(): void;
114
+ /** Open the listbox, activating `startIndex` (default: the selection). */
115
+ openList(startIndex?: number): void;
116
+ /** Close the listbox; `refocus` returns focus to the button. */
117
+ closeList(refocus?: boolean): void;
118
+ protected choose(index: number): void;
119
+ private scrollActiveIntoView;
120
+ private moveActive;
121
+ private runTypeahead;
122
+ protected onButtonKeydown(event: KeyboardEvent): void;
123
+ protected onListKeydown(event: KeyboardEvent): void;
124
+ protected onRootFocusOut(event: FocusEvent): void;
125
+ protected onDocumentClick(event: Event): void;
126
+ private applyMotion;
127
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MotionPicker, never>;
128
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MotionPicker, "lily-motion-picker", never, { "label": { "alias": "label"; "required": true; "isSignal": true; }; "motions": { "alias": "motions"; "required": true; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "defaultValue": { "alias": "defaultValue"; "required": false; "isSignal": true; }; "storageKey": { "alias": "storageKey"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "target": { "alias": "target"; "required": false; "isSignal": true; }; "motionLabels": { "alias": "motionLabels"; "required": false; "isSignal": true; }; "className": { "alias": "className"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "motionChange": "motionChange"; }, ["iconTemplate"], never, true, never>;
129
+ }
130
+
131
+ export { MotionPicker, MotionPickerIcon, motionName, nextMotionPickerId, prefersReducedMotion };
132
+ export type { ChildArgs };
133
+ //# sourceMappingURL=lilydesignsystem-angular-motion-picker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lilydesignsystem-angular-motion-picker.d.ts","sources":["../../motion-picker.component.ts"],"mappings":";;;AAoBA;;;;;;;;AAQG;AAEH;AACM,KAAM,SAAS;;;;;;AAMnB;;AAGF;;;;;AAKG;AACH,iBAAgB,UAAU;AAO1B;;;;AAIG;AACH,iBAAgB,oBAAoB;AAYpC;AACA,iBAAgB,kBAAkB;AAKlC;;;;;;;;;;;;AAYG;AACH,cAIa,gBAAgB;AAC3B,wCACQ,gBAAgB,0BAEb,SAAS;mBAAgB,SAAS;AAAE;oDAJpC,gBAAgB;sDAAhB,gBAAgB;AAO5B;AAED;;;;;;;;;;;;AAYG;AACH,cA6Ea,YAAY;oBACT,aAAA,CAAA,WAAA;sBACE,aAAA,CAAA,WAAA;oBACF,aAAA,CAAA,WAAA;2BACO,aAAA,CAAA,WAAA;yBACF,aAAA,CAAA,WAAA;mBACN,aAAA,CAAA,WAAA;qBACE,aAAA,CAAA,WAAA,CAAA,WAAA;2BACM,aAAA,CAAA,WAAA,CAAA,MAAA;wBACH,aAAA,CAAA,WAAA;2BACG,aAAA,CAAA,gBAAA;;AAGrB,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;;;;;;AA7LrB;;AAoMP;;;;;AAsDH;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;oDA5SW,YAAY;sDAAZ,YAAY;AA6TxB;;","names":[]}