@lilydesignsystem/angular-motion-picker 0.2.0 → 0.3.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.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # MotionPicker (Angular helper)
2
+
3
+ A reusable Angular 20 headless **motion (reduced-motion) picker** — an
4
+ icon button that opens a WAI-ARIA APG listbox of motion-preference
5
+ slugs. On every change it sets `data-motion="{slug}"` on a target
6
+ element (default `document.documentElement`), optionally persisting
7
+ the choice to `localStorage`. Ships no CSS — the consumer decides what
8
+ `data-motion="reduce"` actually suppresses, e.g.:
9
+
10
+ ```css
11
+ :root[data-motion="reduce"] * {
12
+ animation-duration: 0.001ms !important;
13
+ animation-iteration-count: 1 !important;
14
+ transition-duration: 0.001ms !important;
15
+ scroll-behavior: auto !important;
16
+ }
17
+ ```
18
+
19
+ Unlike its `theme-picker` and `text-size-picker` siblings, MotionPicker's
20
+ initial value defers to the platform's own
21
+ `(prefers-reduced-motion: reduce)` media query before falling back to
22
+ an arbitrary default.
23
+
24
+ ## Usage
25
+
26
+ ```ts
27
+ import { Component } from "@angular/core";
28
+ import { MotionPicker } from "@lilydesignsystem/angular-motion-picker";
29
+
30
+ @Component({
31
+ standalone: true,
32
+ imports: [MotionPicker],
33
+ template: `
34
+ <lily-motion-picker
35
+ label="Motion"
36
+ [motions]="['no-preference', 'reduce']"
37
+ [(value)]="motion"
38
+ storageKey="lily-motion"
39
+ />
40
+ `,
41
+ })
42
+ export class Example {
43
+ motion = "";
44
+ }
45
+ ```
46
+
47
+ ## Inputs / Outputs
48
+
49
+ | Name | Type | Required | Description |
50
+ | -------------- | ------------------------ | -------- | ------------------------------------------------------------ |
51
+ | `label` | `string` | yes | Accessible name (`aria-label`) for the button + listbox. |
52
+ | `motions` | `string[]` | yes | Available motion slugs. |
53
+ | `value` | `string` (model) | no | Selected slug. Two-way bindable via `[(value)]`. |
54
+ | `defaultValue` | `string` | no | Initial slug when nothing else is supplied. |
55
+ | `storageKey` | `string` | no | If set, persist the slug to `localStorage`. |
56
+ | `name` | `string` | no | `name` of the hidden input (default `"motion"`). |
57
+ | `target` | `HTMLElement \| null` | no | Element to receive `data-motion`. Default `<html>`. |
58
+ | `motionLabels` | `Record<string,string>` | no | Pretty labels per slug. |
59
+ | `className` | `string` | no | Extra CSS class on the root. |
60
+ | `motionChange` | `output<string>` | — | Emits after a new motion preference is applied. |
61
+
62
+ ## Behaviour
63
+
64
+ Initial value resolves from `value` > storage > `defaultValue` > the
65
+ platform's `(prefers-reduced-motion: reduce)` preference (mapped to
66
+ `"reduce"` / `"no-preference"` if either is in `motions`) > `motions[0]`.
67
+ All DOM writes guard on `typeof document`, so the component is
68
+ SSR-safe.
69
+
70
+ ## Accessibility
71
+
72
+ - WCAG 2.2 AAA target; directly supports 2.3.3 (Animation from
73
+ Interactions).
74
+ - APG listbox keyboard contract: arrows (clamped), `Home` / `End`,
75
+ `PageUp` / `PageDown` (by ten), typeahead with same-character
76
+ cycling, `Escape` discards, and `Tab` closes via the button so the
77
+ default Tab proceeds from the picker's position.
78
+ - `aria-label` carries the consumer-supplied accessible name.
79
+ - Default labels title-case the slug.
80
+
81
+ ---
82
+
83
+ Lily™ and Lily Design System™ are trademarks.
@@ -92,7 +92,11 @@ var MotionPicker = class MotionPicker {
92
92
  this.listRef = viewChild.required("listEl", ...ngDevMode ? [{ debugName: "listRef" }] : /* istanbul ignore next */ []);
93
93
  this.baseId = nextMotionPickerId();
94
94
  this.listId = `${this.baseId}-list`;
95
- this.open = signal(false, ...ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []);
95
+ this.open = model(false, ...ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []);
96
+ this.disabled = input(false, ...ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []);
97
+ this.closeWhenDisabled = effect(() => {
98
+ if (this.disabled() && this.open()) this.open.set(false);
99
+ }, ...ngDevMode ? [{ debugName: "closeWhenDisabled" }] : /* istanbul ignore next */ []);
96
100
  this.tooltipId = `${this.baseId}-tooltip`;
97
101
  this.hoverButton = signal(false, ...ngDevMode ? [{ debugName: "hoverButton" }] : /* istanbul ignore next */ []);
98
102
  this.hoverTooltip = signal(false, ...ngDevMode ? [{ debugName: "hoverTooltip" }] : /* istanbul ignore next */ []);
@@ -302,11 +306,26 @@ var MotionPicker = class MotionPicker {
302
306
  isSignal: true,
303
307
  isRequired: false,
304
308
  transformFunction: null
309
+ },
310
+ open: {
311
+ classPropertyName: "open",
312
+ publicName: "open",
313
+ isSignal: true,
314
+ isRequired: false,
315
+ transformFunction: null
316
+ },
317
+ disabled: {
318
+ classPropertyName: "disabled",
319
+ publicName: "disabled",
320
+ isSignal: true,
321
+ isRequired: false,
322
+ transformFunction: null
305
323
  }
306
324
  },
307
325
  outputs: {
308
326
  value: "valueChange",
309
- motionChange: "motionChange"
327
+ motionChange: "motionChange",
328
+ open: "openChange"
310
329
  },
311
330
  host: { listeners: { "document:click": "onDocumentClick($event)" } },
312
331
  queries: [{
@@ -349,6 +368,7 @@ var MotionPicker = class MotionPicker {
349
368
  <input type="hidden" [name]="name()" [value]="value()" />
350
369
 
351
370
  <lily-icon-button
371
+ [disabled]="disabled()"
352
372
  #buttonEl
353
373
  [label]="label()"
354
374
  baseClass="motion-picker-button"
@@ -508,6 +528,7 @@ i0.ɵɵngDeclareClassMetadata({
508
528
  <input type="hidden" [name]="name()" [value]="value()" />
509
529
 
510
530
  <lily-icon-button
531
+ [disabled]="disabled()"
511
532
  #buttonEl
512
533
  [label]="label()"
513
534
  baseClass="motion-picker-button"
@@ -684,6 +705,25 @@ i0.ɵɵngDeclareClassMetadata({
684
705
  listRef: [{
685
706
  type: i0.ViewChild,
686
707
  args: ["listEl", { isSignal: true }]
708
+ }],
709
+ open: [{
710
+ type: i0.Input,
711
+ args: [{
712
+ isSignal: true,
713
+ alias: "open",
714
+ required: false
715
+ }]
716
+ }, {
717
+ type: i0.Output,
718
+ args: ["openChange"]
719
+ }],
720
+ disabled: [{
721
+ type: i0.Input,
722
+ args: [{
723
+ isSignal: true,
724
+ alias: "disabled",
725
+ required: false
726
+ }]
687
727
  }]
688
728
  }
689
729
  });
@@ -1 +1 @@
1
- {"version":3,"file":"lilydesignsystem-angular-motion-picker.mjs","names":[],"sources":["../../motion-picker.component.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 (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, IconButton, Listbox],\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 <lily-icon-button\n #buttonEl\n [label]=\"label()\"\n baseClass=\"motion-picker-button\"\n ariaHaspopup=\"listbox\"\n [ariaExpanded]=\"open()\"\n [ariaControls]=\"listId\"\n (click)=\"toggle(); hoverButton.set(false)\"\n (mouseenter)=\"onTooltipButtonEnter()\"\n (mouseleave)=\"hoverButton.set(false)\"\n (focusin)=\"onTooltipButtonFocusIn()\"\n (focusout)=\"onTooltipButtonFocusOut()\"\n (keydown)=\"onButtonKeydown($event); onTooltipKeydown($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 </lily-icon-button>\n\n <!-- Purely visual: the same text is already the button's aria-label,\n so it is not wired with aria-describedby (that would announce\n the name twice). -->\n <div\n class=\"motion-picker-tooltip\"\n role=\"tooltip\"\n [id]=\"tooltipId\"\n [attr.hidden]=\"tooltipVisible() ? null : ''\"\n (mouseenter)=\"hoverTooltip.set(true)\"\n (mouseleave)=\"hoverTooltip.set(false)\"\n >{{ label() }}</div>\n\n <lily-listbox\n #listEl\n [label]=\"label()\"\n baseClass=\"motion-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 (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 </lily-listbox>\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 // 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 = nextMotionPickerId();\n protected readonly listId = `${this.baseId}-list`;\n\n protected readonly open = signal(false);\n\n // Tooltip: shown while the pointer is over the button or the tooltip\n // itself (hoverable, WCAG 1.4.13) or while the button has keyboard\n // focus; Escape dismisses it without moving focus; never shown while\n // the popup is open, since the popup then explains the control.\n protected readonly tooltipId = `${this.baseId}-tooltip`;\n protected readonly hoverButton = signal(false);\n protected readonly hoverTooltip = signal(false);\n protected readonly focusButton = signal(false);\n protected readonly dismissed = signal(false);\n protected readonly tooltipVisible = computed(\n () =>\n !this.open() &&\n !this.dismissed() &&\n (this.hoverButton() || this.hoverTooltip() || this.focusButton()),\n );\n\n protected onTooltipButtonEnter(): void {\n this.hoverButton.set(true);\n this.dismissed.set(false);\n }\n\n protected onTooltipButtonFocusIn(): void {\n // Keyboard focus only: a mouse click also focuses the button in\n // Chromium, and the tooltip should not stick after a click.\n try {\n this.focusButton.set(\n this.buttonRef().element?.matches(\":focus-visible\") ?? false,\n );\n } catch {\n this.focusButton.set(true); // engine without :focus-visible — err towards showing\n }\n }\n\n protected onTooltipButtonFocusOut(): void {\n this.focusButton.set(false);\n this.dismissed.set(false);\n }\n\n protected onTooltipKeydown(event: KeyboardEvent): void {\n if (event.key === \"Escape\" && this.tooltipVisible()) this.dismissed.set(true);\n }\n\n // WCAG 1.4.13 \"dismissable\": while the tooltip is visible, Escape works\n // wherever focus is (hover-only tooltips have focus elsewhere). The\n // listener exists only while visible; the effect cleanup removes it on\n // hide and on destroy. No preventDefault/stopPropagation, focus untouched.\n private readonly tooltipEscapeListener = effect((onCleanup) => {\n if (!this.tooltipVisible() || typeof document === \"undefined\") return;\n const onKey = (event: KeyboardEvent): void => {\n if (event.key === \"Escape\") this.dismissed.set(true);\n };\n document.addEventListener(\"keydown\", onKey);\n onCleanup(() => document.removeEventListener(\"keydown\", onKey));\n });\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: (motion: string) => this.labelFor(motion),\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 // 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().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.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 // 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.motions().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 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"],"mappings":"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"}
1
+ {"version":3,"file":"lilydesignsystem-angular-motion-picker.mjs","names":[],"sources":["../../motion-picker.component.ts"],"sourcesContent":["import { NgTemplateOutlet } from \"@angular/common\";\nimport {\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n computed,\n contentChild,\n Directive,\n effect,\n ElementRef,\n inject,\n input,\n model,\n output,\n signal,\n TemplateRef,\n viewChild,\n} from \"@angular/core\";\nimport { IconButton, Listbox } from \"@lilydesignsystem/angular-headless\";\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, IconButton, Listbox],\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 <lily-icon-button\n [disabled]=\"disabled()\"\n #buttonEl\n [label]=\"label()\"\n baseClass=\"motion-picker-button\"\n ariaHaspopup=\"listbox\"\n [ariaExpanded]=\"open()\"\n [ariaControls]=\"listId\"\n (click)=\"toggle(); hoverButton.set(false)\"\n (mouseenter)=\"onTooltipButtonEnter()\"\n (mouseleave)=\"hoverButton.set(false)\"\n (focusin)=\"onTooltipButtonFocusIn()\"\n (focusout)=\"onTooltipButtonFocusOut()\"\n (keydown)=\"onButtonKeydown($event); onTooltipKeydown($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 </lily-icon-button>\n\n <!-- Purely visual: the same text is already the button's aria-label,\n so it is not wired with aria-describedby (that would announce\n the name twice). -->\n <div\n class=\"motion-picker-tooltip\"\n role=\"tooltip\"\n [id]=\"tooltipId\"\n [attr.hidden]=\"tooltipVisible() ? null : ''\"\n (mouseenter)=\"hoverTooltip.set(true)\"\n (mouseleave)=\"hoverTooltip.set(false)\"\n >{{ label() }}</div>\n\n <lily-listbox\n #listEl\n [label]=\"label()\"\n baseClass=\"motion-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 (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 </lily-listbox>\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 // 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 = nextMotionPickerId();\n protected readonly listId = `${this.baseId}-list`;\n\n /** Is the popup open? Two-way bindable: `[(open)]`; `openChange` fires for each change. */\n readonly open = model<boolean>(false);\n /** Disables the button; an open popup closes. */\n readonly disabled = input<boolean>(false);\n private readonly closeWhenDisabled = effect(() => {\n if (this.disabled() && this.open()) this.open.set(false);\n });\n\n // Tooltip: shown while the pointer is over the button or the tooltip\n // itself (hoverable, WCAG 1.4.13) or while the button has keyboard\n // focus; Escape dismisses it without moving focus; never shown while\n // the popup is open, since the popup then explains the control.\n protected readonly tooltipId = `${this.baseId}-tooltip`;\n protected readonly hoverButton = signal(false);\n protected readonly hoverTooltip = signal(false);\n protected readonly focusButton = signal(false);\n protected readonly dismissed = signal(false);\n protected readonly tooltipVisible = computed(\n () =>\n !this.open() &&\n !this.dismissed() &&\n (this.hoverButton() || this.hoverTooltip() || this.focusButton()),\n );\n\n protected onTooltipButtonEnter(): void {\n this.hoverButton.set(true);\n this.dismissed.set(false);\n }\n\n protected onTooltipButtonFocusIn(): void {\n // Keyboard focus only: a mouse click also focuses the button in\n // Chromium, and the tooltip should not stick after a click.\n try {\n this.focusButton.set(\n this.buttonRef().element?.matches(\":focus-visible\") ?? false,\n );\n } catch {\n this.focusButton.set(true); // engine without :focus-visible — err towards showing\n }\n }\n\n protected onTooltipButtonFocusOut(): void {\n this.focusButton.set(false);\n this.dismissed.set(false);\n }\n\n protected onTooltipKeydown(event: KeyboardEvent): void {\n if (event.key === \"Escape\" && this.tooltipVisible()) this.dismissed.set(true);\n }\n\n // WCAG 1.4.13 \"dismissable\": while the tooltip is visible, Escape works\n // wherever focus is (hover-only tooltips have focus elsewhere). The\n // listener exists only while visible; the effect cleanup removes it on\n // hide and on destroy. No preventDefault/stopPropagation, focus untouched.\n private readonly tooltipEscapeListener = effect((onCleanup) => {\n if (!this.tooltipVisible() || typeof document === \"undefined\") return;\n const onKey = (event: KeyboardEvent): void => {\n if (event.key === \"Escape\") this.dismissed.set(true);\n };\n document.addEventListener(\"keydown\", onKey);\n onCleanup(() => document.removeEventListener(\"keydown\", onKey));\n });\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: (motion: string) => this.labelFor(motion),\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 // 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().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.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 // 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.motions().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 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"],"mappings":"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"}
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@lilydesignsystem/angular-motion-picker",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "engines": {
5
5
  "node": "=26"
6
6
  },
7
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
8
  "dependencies": {
9
- "@lilydesignsystem/angular-headless": "^0.2.0",
9
+ "@lilydesignsystem/angular-headless": "^0.5.0",
10
10
  "tslib": "^2.3.0"
11
11
  },
12
12
  "peerDependencies": {
@@ -27,7 +27,7 @@
27
27
  "license": "MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause",
28
28
  "repository": {
29
29
  "type": "git",
30
- "url": "git+https://github.com/LilyDesignSystem/lily-design-system-angular-helpers.git",
30
+ "url": "git+https://github.com/LilyDesignSystem/lily-design-system.git",
31
31
  "directory": "lily-design-system-angular-motion-picker"
32
32
  },
33
33
  "homepage": "https://lilydesignsystem.com/",
@@ -67,37 +67,41 @@ export declare class MotionPickerIcon {
67
67
  * See `spec/index.md` for the full contract.
68
68
  */
69
69
  export declare class MotionPicker {
70
- readonly label: import("@angular/core").InputSignal<string>;
71
- readonly motions: import("@angular/core").InputSignal<string[]>;
72
- readonly value: import("@angular/core").ModelSignal<string>;
73
- readonly defaultValue: import("@angular/core").InputSignal<string>;
74
- readonly storageKey: import("@angular/core").InputSignal<string>;
75
- readonly name: import("@angular/core").InputSignal<string>;
76
- readonly target: import("@angular/core").InputSignal<HTMLElement | null>;
77
- readonly motionLabels: import("@angular/core").InputSignal<Record<string, string>>;
78
- readonly className: import("@angular/core").InputSignal<string>;
79
- readonly motionChange: import("@angular/core").OutputEmitterRef<string>;
70
+ readonly label: import("@angular/core/types/core").InputSignal<string>;
71
+ readonly motions: import("@angular/core/types/core").InputSignal<string[]>;
72
+ readonly value: import("@angular/core/types/core").ModelSignal<string>;
73
+ readonly defaultValue: import("@angular/core/types/core").InputSignal<string>;
74
+ readonly storageKey: import("@angular/core/types/core").InputSignal<string>;
75
+ readonly name: import("@angular/core/types/core").InputSignal<string>;
76
+ readonly target: import("@angular/core/types/core").InputSignal<HTMLElement | null>;
77
+ readonly motionLabels: import("@angular/core/types/core").InputSignal<Record<string, string>>;
78
+ readonly className: import("@angular/core/types/core").InputSignal<string>;
79
+ readonly motionChange: import("@angular/core/types/_api-chunk").OutputEmitterRef<string>;
80
80
  /** Projected icon template; replaces the default icon when supplied. */
81
- protected readonly iconTemplate: import("@angular/core").Signal<TemplateRef<any> | undefined>;
81
+ protected readonly iconTemplate: import("@angular/core/types/_chrome_dev_tools_performance-chunk").Signal<TemplateRef<any> | undefined>;
82
82
  private readonly rootRef;
83
83
  private readonly buttonRef;
84
84
  private readonly listRef;
85
85
  private readonly baseId;
86
86
  protected readonly listId: string;
87
- protected readonly open: import("@angular/core").WritableSignal<boolean>;
87
+ /** Is the popup open? Two-way bindable: `[(open)]`; `openChange` fires for each change. */
88
+ readonly open: import("@angular/core/types/core").ModelSignal<boolean>;
89
+ /** Disables the button; an open popup closes. */
90
+ readonly disabled: import("@angular/core/types/core").InputSignal<boolean>;
91
+ private readonly closeWhenDisabled;
88
92
  protected readonly tooltipId: string;
89
- protected readonly hoverButton: import("@angular/core").WritableSignal<boolean>;
90
- protected readonly hoverTooltip: import("@angular/core").WritableSignal<boolean>;
91
- protected readonly focusButton: import("@angular/core").WritableSignal<boolean>;
92
- protected readonly dismissed: import("@angular/core").WritableSignal<boolean>;
93
- protected readonly tooltipVisible: import("@angular/core").Signal<boolean>;
93
+ protected readonly hoverButton: import("@angular/core/types/_chrome_dev_tools_performance-chunk").WritableSignal<boolean>;
94
+ protected readonly hoverTooltip: import("@angular/core/types/_chrome_dev_tools_performance-chunk").WritableSignal<boolean>;
95
+ protected readonly focusButton: import("@angular/core/types/_chrome_dev_tools_performance-chunk").WritableSignal<boolean>;
96
+ protected readonly dismissed: import("@angular/core/types/_chrome_dev_tools_performance-chunk").WritableSignal<boolean>;
97
+ protected readonly tooltipVisible: import("@angular/core/types/_chrome_dev_tools_performance-chunk").Signal<boolean>;
94
98
  protected onTooltipButtonEnter(): void;
95
99
  protected onTooltipButtonFocusIn(): void;
96
100
  protected onTooltipButtonFocusOut(): void;
97
101
  protected onTooltipKeydown(event: KeyboardEvent): void;
98
102
  private readonly tooltipEscapeListener;
99
- protected readonly activeIndex: import("@angular/core").WritableSignal<number>;
100
- protected readonly childContext: import("@angular/core").Signal<{
103
+ protected readonly activeIndex: import("@angular/core/types/_chrome_dev_tools_performance-chunk").WritableSignal<number>;
104
+ protected readonly childContext: import("@angular/core/types/_chrome_dev_tools_performance-chunk").Signal<{
101
105
  /** Currently selected motion slug. */
102
106
  value: string;
103
107
  /** Is the listbox open? */
@@ -181,9 +185,20 @@ export declare class MotionPicker {
181
185
  "required": false;
182
186
  "isSignal": true;
183
187
  };
188
+ "open": {
189
+ "alias": "open";
190
+ "required": false;
191
+ "isSignal": true;
192
+ };
193
+ "disabled": {
194
+ "alias": "disabled";
195
+ "required": false;
196
+ "isSignal": true;
197
+ };
184
198
  }, {
185
199
  "value": "valueChange";
186
200
  "motionChange": "motionChange";
201
+ "open": "openChange";
187
202
  }, ["iconTemplate"], never, true, never>;
188
203
  }
189
204
  export type { ChildArgs };
@@ -1 +1 @@
1
- {"version":3,"file":"lilydesignsystem-angular-motion-picker.d.ts","names":[],"sources":["../../motion-picker.component.ts"],"mappings":";;;;;;;;;;;;KA+BY;;EAEV;;EAEA;;EAEA,WAAW;;;;;;;;wBASG,WAAW;;;;;;wBAYX;;wBAaA;;;;;;;;;;;;;;qBAsBH;SACJ,uBACL,MAAM,kBACN,gBACC,QAAQ;IAAc,WAAW;;SAJzB,MAAA,GAAA,qBAAA;SAAA,MAAA,GAAA,uBAAA;;;;;;;;;;;;;;;qBAsHA;WACF,+BAAK;WACL,iCAAO;WACP,+BAAK;WACL,sCAAY;WACZ,oCAAU;WACV,8BAAI;WACJ,gCAAM,YAAA;WACN,sCAAY,YAAA;WACZ,mCAAS;WACT,sCAAY;;qBAGF,sCAAY,OAAA;mBAEd;mBAKA;mBACA;mBAEA;qBACE;qBAEA,8BAAI;qBAMJ;qBACA,qCAAW;qBACX,sCAAY;qBACZ,qCAAW;qBACX,mCAAS;qBACT,wCAAc;YAOvB;YAKA;YAYA;YAKA,iBAAiB,OAAO;mBAQjB;qBAQE,qCAAW;qBAEX,sCAAY;;IAtQxB;;IAED;;IAEI,WAAC;;;UA2QH;;YA8CE,SAAS;EAInB,SAAS;;;;;;mBAeQ;YAEP;;EAMV,SAAS;;EAqBT,UAAU;YAOA,OAAO;UAMT;mBAmBS;YASP,gBAAgB,OAAO;;;;;;YAoBvB;YAKA,eAAe,OAAO;YAMtB,gBAAgB,OAAO;UAUzB;SA7QG,MAAA,GAAA,qBAAA;SAAA,MAAA,GAAA,uBAAA;IAAY;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;;IAAA;IAAA"}
1
+ {"version":3,"file":"lilydesignsystem-angular-motion-picker.d.ts","names":[],"sources":["../../motion-picker.component.ts"],"mappings":";;;;;;;;;;;;KA+BY;;EAEV;;EAEA;;EAEA,WAAW;;;;;;;;wBASG,WAAW;;;;;;wBAYX;;wBAaA;;;;;;;;;;;;;;qBAsBH;SACJ,uBACL,MAAM,kBACN,gBACC,QAAQ;IAAc,WAAW;;SAJzB,MAAA,GAAA,qBAAA;SAAA,MAAA,GAAA,uBAAA;;;;;;;;;;;;;;;qBAuHA;WACF,0CAAK;WACL,4CAAO;WACP,0CAAK;WACL,iDAAY;WACZ,+CAAU;WACV,yCAAI;WACJ,2CAAM,YAAA;WACN,iDAAY,YAAA;WACZ,8CAAS;WACT,uDAAY;;qBAGF,gFAAY,OAAA;mBAEd;mBAKA;mBACA;mBAEA;qBACE;;WAGV,yCAAI;;WAEJ,6CAAQ;mBACA;qBAQE;qBACA,+EAAW;qBACX,gFAAY;qBACZ,+EAAW;qBACX,6EAAS;qBACT,kFAAc;YAOvB;YAKA;YAYA;YAKA,iBAAiB,OAAO;mBAQjB;qBAQE,+EAAW;qBAEX,gFAAY;;IA7QxB;;IAED;;IAEI,WAAC;;;UAkRH;;YA8CE,SAAS;EAInB,SAAS;;;;;;mBAeQ;YAEP;;EAMV,SAAS;;EAqBT,UAAU;YAOA,OAAO;UAMT;mBAmBS;YASP,gBAAgB,OAAO;;;;;;YAoBvB;YAKA,eAAe,OAAO;YAMtB,gBAAgB,OAAO;UAUzB;SAnRG,MAAA,GAAA,qBAAA;SAAA,MAAA,GAAA,uBAAA;IAAY;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;;IAAA;IAAA;IAAA"}