@lilydesignsystem/angular-link-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.
- package/README.md +86 -0
- package/fesm2022/lilydesignsystem-angular-link-picker.mjs +546 -0
- package/fesm2022/lilydesignsystem-angular-link-picker.mjs.map +1 -0
- package/package.json +50 -0
- package/types/lilydesignsystem-angular-link-picker.d.ts +137 -0
- package/types/lilydesignsystem-angular-link-picker.d.ts.map +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# LinkPicker (Angular)
|
|
2
|
+
|
|
3
|
+
A headless control for **page links**: a single-icon button (a bundled home SVG) that opens a dropdown of links **your app
|
|
4
|
+
defines** — "Home", "About Us", "Contact Us", "Privacy Policy". No CSS, no routes, no English.
|
|
5
|
+
|
|
6
|
+
The single source of truth is [spec/index.md](./spec/index.md); this file is the guide.
|
|
7
|
+
|
|
8
|
+
## Quick start
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { Component } from "@angular/core";
|
|
12
|
+
import { LinkPicker, type LinkItem } from "@lilydesignsystem/angular-link-picker";
|
|
13
|
+
|
|
14
|
+
@Component({
|
|
15
|
+
selector: "app-header",
|
|
16
|
+
standalone: true,
|
|
17
|
+
imports: [LinkPicker],
|
|
18
|
+
template: `<lily-link-picker label="Pages" [links]="links" />`,
|
|
19
|
+
})
|
|
20
|
+
export class AppHeader {
|
|
21
|
+
links: LinkItem[] = [
|
|
22
|
+
{ label: "Home", href: "/" },
|
|
23
|
+
{ label: "About Us", href: "/about/" },
|
|
24
|
+
{ label: "Contact Us", href: "/contact/" },
|
|
25
|
+
{ label: "Privacy Policy", href: "/privacy/" },
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`label` names the button, the list and the tooltip. There is **no default list**: the package ships no routes and no English.
|
|
31
|
+
|
|
32
|
+
## Client-side navigation
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { Component, inject } from "@angular/core";
|
|
36
|
+
import { Router } from "@angular/router";
|
|
37
|
+
import { LinkPicker } from "@lilydesignsystem/angular-link-picker";
|
|
38
|
+
|
|
39
|
+
@Component({
|
|
40
|
+
selector: "app-header",
|
|
41
|
+
standalone: true,
|
|
42
|
+
imports: [LinkPicker],
|
|
43
|
+
template: `<lily-link-picker label="Pages" [links]="links()" [navigate]="go" />`,
|
|
44
|
+
})
|
|
45
|
+
export class AppHeader {
|
|
46
|
+
private router = inject(Router);
|
|
47
|
+
go = (href: string) => void this.router.navigateByUrl(href);
|
|
48
|
+
links = () => [
|
|
49
|
+
{ label: "Home", href: "/" },
|
|
50
|
+
{ label: "About Us", href: "/about/" },
|
|
51
|
+
{ label: "Contact Us", href: "/contact/" },
|
|
52
|
+
{ label: "Privacy Policy", href: "/privacy/" },
|
|
53
|
+
].map((p) => ({ ...p, current: this.router.url === p.href }));
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Ctrl/Cmd/Shift/Alt-click, middle-click and `newTab` links always stay native browser behaviour.
|
|
58
|
+
|
|
59
|
+
## `LinkItem`
|
|
60
|
+
|
|
61
|
+
| Field | Notes |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| label | Visible text. Yours, so it localises. |
|
|
64
|
+
| href | A route (`"/about/"`) or a full URL. |
|
|
65
|
+
| id | Reported when a link is chosen; defaults to the href. |
|
|
66
|
+
| current | Marks the current page with `aria-current="page"`. |
|
|
67
|
+
| newTab | `target="_blank"` with `rel="noopener noreferrer"`. |
|
|
68
|
+
|
|
69
|
+
## Props
|
|
70
|
+
|
|
71
|
+
| `label` | `input.required<string>()` | yes | Accessible name of the button **and** the list; also the tooltip text. |
|
|
72
|
+
| `links` | `input.required<LinkItem[]>()` | yes | `{ id?, label, href, current?, newTab? }`. `id` defaults to `href`. |
|
|
73
|
+
| `navigate` | `input<(href: string) => void>()` | no | Client-side navigation hook (a router's `navigateByUrl`). |
|
|
74
|
+
| `navigated` | `output<{ id, href }>()` | no | Emitted after a link is chosen (the Svelte `onNavigate` callback). |
|
|
75
|
+
| icon template | `<ng-template lilyLinkPickerIcon let-args>` | no | Replaces the **icon**, not the links. |
|
|
76
|
+
| `className` | `input<string>()` | no | Appended to the root class. |
|
|
77
|
+
|
|
78
|
+
## Styling
|
|
79
|
+
|
|
80
|
+
Headless: no CSS ships. Hooks: `.link-picker`, `.link-picker-button`, `.link-picker-icon`, `.link-picker-tooltip`,
|
|
81
|
+
`.link-picker-list` (carries `hidden` when closed), `.link-picker-list-item`, `.link-picker-link`. The monorepo's 45 `themes/`
|
|
82
|
+
style them all, including the popup placement.
|
|
83
|
+
|
|
84
|
+
## Examples
|
|
85
|
+
|
|
86
|
+
[examples/](./examples/README.md): the four-link example, router integration and a custom icon.
|
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import { NgTemplateOutlet } from "@angular/common";
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
import { ChangeDetectionStrategy, Component, Directive, TemplateRef, computed, contentChild, effect, input, output, signal, viewChild } from "@angular/core";
|
|
4
|
+
import { IconButton } from "@lilydesignsystem/angular-headless";
|
|
5
|
+
function linkId(link) {
|
|
6
|
+
return link.id ?? link.href;
|
|
7
|
+
}
|
|
8
|
+
let uid = 0;
|
|
9
|
+
function nextLinkPickerId() {
|
|
10
|
+
uid += 1;
|
|
11
|
+
return `link-picker-${uid}`;
|
|
12
|
+
}
|
|
13
|
+
var LinkPickerIcon = class LinkPickerIcon {
|
|
14
|
+
static ngTemplateContextGuard(_dir, _ctx) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
static {
|
|
18
|
+
this.ɵfac = i0.ɵɵngDeclareFactory({
|
|
19
|
+
minVersion: "12.0.0",
|
|
20
|
+
version: "22.2.1",
|
|
21
|
+
ngImport: i0,
|
|
22
|
+
type: LinkPickerIcon,
|
|
23
|
+
deps: [],
|
|
24
|
+
target: i0.ɵɵFactoryTarget.Directive
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
static {
|
|
28
|
+
this.ɵdir = i0.ɵɵngDeclareDirective({
|
|
29
|
+
minVersion: "14.0.0",
|
|
30
|
+
version: "22.2.1",
|
|
31
|
+
type: LinkPickerIcon,
|
|
32
|
+
isStandalone: true,
|
|
33
|
+
selector: "ng-template[lilyLinkPickerIcon]",
|
|
34
|
+
ngImport: i0
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
39
|
+
minVersion: "12.0.0",
|
|
40
|
+
version: "22.2.1",
|
|
41
|
+
ngImport: i0,
|
|
42
|
+
type: LinkPickerIcon,
|
|
43
|
+
decorators: [{
|
|
44
|
+
type: Directive,
|
|
45
|
+
args: [{
|
|
46
|
+
selector: "ng-template[lilyLinkPickerIcon]",
|
|
47
|
+
standalone: true
|
|
48
|
+
}]
|
|
49
|
+
}]
|
|
50
|
+
});
|
|
51
|
+
var LinkPicker = class LinkPicker {
|
|
52
|
+
constructor() {
|
|
53
|
+
this.label = input.required(...ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []);
|
|
54
|
+
this.links = input.required(...ngDevMode ? [{ debugName: "links" }] : /* istanbul ignore next */ []);
|
|
55
|
+
this.navigate = input(void 0, ...ngDevMode ? [{ debugName: "navigate" }] : /* istanbul ignore next */ []);
|
|
56
|
+
this.className = input("", ...ngDevMode ? [{ debugName: "className" }] : /* istanbul ignore next */ []);
|
|
57
|
+
this.navigated = output();
|
|
58
|
+
this.iconTemplate = contentChild(TemplateRef, ...ngDevMode ? [{ debugName: "iconTemplate" }] : /* istanbul ignore next */ []);
|
|
59
|
+
this.rootRef = viewChild.required("rootEl", ...ngDevMode ? [{ debugName: "rootRef" }] : /* istanbul ignore next */ []);
|
|
60
|
+
this.buttonRef = viewChild.required("buttonEl", ...ngDevMode ? [{ debugName: "buttonRef" }] : /* istanbul ignore next */ []);
|
|
61
|
+
this.listRef = viewChild.required("listEl", ...ngDevMode ? [{ debugName: "listRef" }] : /* istanbul ignore next */ []);
|
|
62
|
+
this.baseId = nextLinkPickerId();
|
|
63
|
+
this.listId = `${this.baseId}-list`;
|
|
64
|
+
this.open = signal(false, ...ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []);
|
|
65
|
+
this.tooltipId = `${this.baseId}-tooltip`;
|
|
66
|
+
this.hoverButton = signal(false, ...ngDevMode ? [{ debugName: "hoverButton" }] : /* istanbul ignore next */ []);
|
|
67
|
+
this.hoverTooltip = signal(false, ...ngDevMode ? [{ debugName: "hoverTooltip" }] : /* istanbul ignore next */ []);
|
|
68
|
+
this.focusButton = signal(false, ...ngDevMode ? [{ debugName: "focusButton" }] : /* istanbul ignore next */ []);
|
|
69
|
+
this.dismissed = signal(false, ...ngDevMode ? [{ debugName: "dismissed" }] : /* istanbul ignore next */ []);
|
|
70
|
+
this.tooltipVisible = computed(() => !this.open() && !this.dismissed() && (this.hoverButton() || this.hoverTooltip() || this.focusButton()), ...ngDevMode ? [{ debugName: "tooltipVisible" }] : /* istanbul ignore next */ []);
|
|
71
|
+
this.tooltipEscapeListener = effect((onCleanup) => {
|
|
72
|
+
if (!this.tooltipVisible() || typeof document === "undefined") return;
|
|
73
|
+
const onKey = (event) => {
|
|
74
|
+
if (event.key === "Escape") this.dismissed.set(true);
|
|
75
|
+
};
|
|
76
|
+
document.addEventListener("keydown", onKey);
|
|
77
|
+
onCleanup(() => document.removeEventListener("keydown", onKey));
|
|
78
|
+
}, ...ngDevMode ? [{ debugName: "tooltipEscapeListener" }] : /* istanbul ignore next */ []);
|
|
79
|
+
this.childContext = computed(() => {
|
|
80
|
+
const args = { open: this.open() };
|
|
81
|
+
return {
|
|
82
|
+
$implicit: args,
|
|
83
|
+
...args
|
|
84
|
+
};
|
|
85
|
+
}, ...ngDevMode ? [{ debugName: "childContext" }] : /* istanbul ignore next */ []);
|
|
86
|
+
}
|
|
87
|
+
onTooltipButtonEnter() {
|
|
88
|
+
this.hoverButton.set(true);
|
|
89
|
+
this.dismissed.set(false);
|
|
90
|
+
}
|
|
91
|
+
onTooltipButtonFocusIn() {
|
|
92
|
+
try {
|
|
93
|
+
this.focusButton.set(this.buttonRef().element?.matches(":focus-visible") ?? false);
|
|
94
|
+
} catch {
|
|
95
|
+
this.focusButton.set(true);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
onTooltipButtonFocusOut() {
|
|
99
|
+
this.focusButton.set(false);
|
|
100
|
+
this.dismissed.set(false);
|
|
101
|
+
}
|
|
102
|
+
onTooltipKeydown(event) {
|
|
103
|
+
if (event.key === "Escape" && this.tooltipVisible()) this.dismissed.set(true);
|
|
104
|
+
}
|
|
105
|
+
idOf(link) {
|
|
106
|
+
return linkId(link);
|
|
107
|
+
}
|
|
108
|
+
items() {
|
|
109
|
+
return Array.from(this.listRef().nativeElement.querySelectorAll(".link-picker-link"));
|
|
110
|
+
}
|
|
111
|
+
openList(focusLast = false) {
|
|
112
|
+
this.open.set(true);
|
|
113
|
+
queueMicrotask(() => {
|
|
114
|
+
const all = this.items();
|
|
115
|
+
(focusLast ? all[all.length - 1] : all[0])?.focus({ preventScroll: true });
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
closeList(refocus = true) {
|
|
119
|
+
if (!this.open()) return;
|
|
120
|
+
this.open.set(false);
|
|
121
|
+
if (refocus) queueMicrotask(() => this.buttonRef().focus({ preventScroll: true }));
|
|
122
|
+
}
|
|
123
|
+
onButtonClick() {
|
|
124
|
+
if (this.open()) this.closeList();
|
|
125
|
+
else this.openList();
|
|
126
|
+
}
|
|
127
|
+
onLinkClick(event, link) {
|
|
128
|
+
this.navigated.emit({
|
|
129
|
+
id: linkId(link),
|
|
130
|
+
href: link.href
|
|
131
|
+
});
|
|
132
|
+
const go = this.navigate();
|
|
133
|
+
const modified = event.ctrlKey || event.metaKey || event.shiftKey || event.altKey || event.button !== 0;
|
|
134
|
+
if (go && !link.newTab && !modified && !event.defaultPrevented) {
|
|
135
|
+
event.preventDefault();
|
|
136
|
+
go(link.href);
|
|
137
|
+
}
|
|
138
|
+
this.closeList();
|
|
139
|
+
}
|
|
140
|
+
onButtonKeydown(event) {
|
|
141
|
+
if (event.key === "ArrowDown") {
|
|
142
|
+
event.preventDefault();
|
|
143
|
+
if (!this.open()) this.openList();
|
|
144
|
+
else this.items()[0]?.focus({ preventScroll: true });
|
|
145
|
+
} else if (event.key === "ArrowUp") {
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
if (!this.open()) this.openList(true);
|
|
148
|
+
else {
|
|
149
|
+
const all = this.items();
|
|
150
|
+
all[all.length - 1]?.focus({ preventScroll: true });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
moveFocus(delta) {
|
|
155
|
+
const all = this.items();
|
|
156
|
+
if (all.length === 0) return;
|
|
157
|
+
const i = all.indexOf(document.activeElement);
|
|
158
|
+
all[Math.min(Math.max((i < 0 ? 0 : i) + delta, 0), all.length - 1)]?.focus({ preventScroll: true });
|
|
159
|
+
}
|
|
160
|
+
onListKeydown(event) {
|
|
161
|
+
switch (event.key) {
|
|
162
|
+
case "ArrowDown":
|
|
163
|
+
event.preventDefault();
|
|
164
|
+
this.moveFocus(1);
|
|
165
|
+
break;
|
|
166
|
+
case "ArrowUp":
|
|
167
|
+
event.preventDefault();
|
|
168
|
+
this.moveFocus(-1);
|
|
169
|
+
break;
|
|
170
|
+
case "Home":
|
|
171
|
+
event.preventDefault();
|
|
172
|
+
this.items()[0]?.focus({ preventScroll: true });
|
|
173
|
+
break;
|
|
174
|
+
case "End": {
|
|
175
|
+
event.preventDefault();
|
|
176
|
+
const all = this.items();
|
|
177
|
+
all[all.length - 1]?.focus({ preventScroll: true });
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
case "Escape":
|
|
181
|
+
event.preventDefault();
|
|
182
|
+
this.closeList();
|
|
183
|
+
break;
|
|
184
|
+
case "Tab":
|
|
185
|
+
this.buttonRef().focus?.({ preventScroll: true });
|
|
186
|
+
this.closeList(false);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
onRootFocusOut(event) {
|
|
190
|
+
const next = event.relatedTarget;
|
|
191
|
+
if (next && this.rootRef().nativeElement.contains(next)) return;
|
|
192
|
+
this.closeList(false);
|
|
193
|
+
}
|
|
194
|
+
onDocumentClick(event) {
|
|
195
|
+
if (!this.open()) return;
|
|
196
|
+
const t = event.target;
|
|
197
|
+
if (t && !this.rootRef().nativeElement.contains(t)) this.closeList(false);
|
|
198
|
+
}
|
|
199
|
+
static {
|
|
200
|
+
this.ɵfac = i0.ɵɵngDeclareFactory({
|
|
201
|
+
minVersion: "12.0.0",
|
|
202
|
+
version: "22.2.1",
|
|
203
|
+
ngImport: i0,
|
|
204
|
+
type: LinkPicker,
|
|
205
|
+
deps: [],
|
|
206
|
+
target: i0.ɵɵFactoryTarget.Component
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
static {
|
|
210
|
+
this.ɵcmp = i0.ɵɵngDeclareComponent({
|
|
211
|
+
minVersion: "17.0.0",
|
|
212
|
+
version: "22.2.1",
|
|
213
|
+
type: LinkPicker,
|
|
214
|
+
isStandalone: true,
|
|
215
|
+
selector: "lily-link-picker",
|
|
216
|
+
inputs: {
|
|
217
|
+
label: {
|
|
218
|
+
classPropertyName: "label",
|
|
219
|
+
publicName: "label",
|
|
220
|
+
isSignal: true,
|
|
221
|
+
isRequired: true,
|
|
222
|
+
transformFunction: null
|
|
223
|
+
},
|
|
224
|
+
links: {
|
|
225
|
+
classPropertyName: "links",
|
|
226
|
+
publicName: "links",
|
|
227
|
+
isSignal: true,
|
|
228
|
+
isRequired: true,
|
|
229
|
+
transformFunction: null
|
|
230
|
+
},
|
|
231
|
+
navigate: {
|
|
232
|
+
classPropertyName: "navigate",
|
|
233
|
+
publicName: "navigate",
|
|
234
|
+
isSignal: true,
|
|
235
|
+
isRequired: false,
|
|
236
|
+
transformFunction: null
|
|
237
|
+
},
|
|
238
|
+
className: {
|
|
239
|
+
classPropertyName: "className",
|
|
240
|
+
publicName: "className",
|
|
241
|
+
isSignal: true,
|
|
242
|
+
isRequired: false,
|
|
243
|
+
transformFunction: null
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
outputs: { navigated: "navigated" },
|
|
247
|
+
host: { listeners: { "document:click": "onDocumentClick($event)" } },
|
|
248
|
+
queries: [{
|
|
249
|
+
propertyName: "iconTemplate",
|
|
250
|
+
first: true,
|
|
251
|
+
predicate: TemplateRef,
|
|
252
|
+
descendants: true,
|
|
253
|
+
isSignal: true
|
|
254
|
+
}],
|
|
255
|
+
viewQueries: [
|
|
256
|
+
{
|
|
257
|
+
propertyName: "rootRef",
|
|
258
|
+
first: true,
|
|
259
|
+
predicate: ["rootEl"],
|
|
260
|
+
descendants: true,
|
|
261
|
+
isSignal: true
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
propertyName: "buttonRef",
|
|
265
|
+
first: true,
|
|
266
|
+
predicate: ["buttonEl"],
|
|
267
|
+
descendants: true,
|
|
268
|
+
isSignal: true
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
propertyName: "listRef",
|
|
272
|
+
first: true,
|
|
273
|
+
predicate: ["listEl"],
|
|
274
|
+
descendants: true,
|
|
275
|
+
isSignal: true
|
|
276
|
+
}
|
|
277
|
+
],
|
|
278
|
+
ngImport: i0,
|
|
279
|
+
template: `
|
|
280
|
+
<div
|
|
281
|
+
#rootEl
|
|
282
|
+
class="link-picker {{ className() }}"
|
|
283
|
+
(focusout)="onRootFocusOut($event)"
|
|
284
|
+
>
|
|
285
|
+
<lily-icon-button
|
|
286
|
+
#buttonEl
|
|
287
|
+
[label]="label()"
|
|
288
|
+
baseClass="link-picker-button"
|
|
289
|
+
[ariaExpanded]="open()"
|
|
290
|
+
[ariaControls]="listId"
|
|
291
|
+
(click)="onButtonClick(); hoverButton.set(false)"
|
|
292
|
+
(mouseenter)="onTooltipButtonEnter()"
|
|
293
|
+
(mouseleave)="hoverButton.set(false)"
|
|
294
|
+
(focusin)="onTooltipButtonFocusIn()"
|
|
295
|
+
(focusout)="onTooltipButtonFocusOut()"
|
|
296
|
+
(keydown)="onButtonKeydown($event); onTooltipKeydown($event)"
|
|
297
|
+
>
|
|
298
|
+
@if (iconTemplate(); as tpl) {
|
|
299
|
+
<ng-container
|
|
300
|
+
[ngTemplateOutlet]="tpl"
|
|
301
|
+
[ngTemplateOutletContext]="childContext()"
|
|
302
|
+
/>
|
|
303
|
+
} @else {
|
|
304
|
+
<svg
|
|
305
|
+
class="link-picker-icon"
|
|
306
|
+
viewBox="0 0 16 16"
|
|
307
|
+
width="1.05rem"
|
|
308
|
+
height="1.05rem"
|
|
309
|
+
aria-hidden="true"
|
|
310
|
+
fill="none"
|
|
311
|
+
stroke="currentColor"
|
|
312
|
+
stroke-width="1.6"
|
|
313
|
+
stroke-linecap="round"
|
|
314
|
+
stroke-linejoin="round"
|
|
315
|
+
>
|
|
316
|
+
<path d="M2 7.5 8 2.5l6 5" />
|
|
317
|
+
<path d="M3.5 6.5v7h9v-7" />
|
|
318
|
+
<path d="M6.5 13.5V10h3v3.5" />
|
|
319
|
+
</svg>
|
|
320
|
+
}
|
|
321
|
+
</lily-icon-button>
|
|
322
|
+
|
|
323
|
+
<!-- Purely visual: the same text is already the button's aria-label, so it is not wired
|
|
324
|
+
with aria-describedby (that would announce the name twice). -->
|
|
325
|
+
<div
|
|
326
|
+
class="link-picker-tooltip"
|
|
327
|
+
role="tooltip"
|
|
328
|
+
[id]="tooltipId"
|
|
329
|
+
[attr.hidden]="tooltipVisible() ? null : ''"
|
|
330
|
+
(mouseenter)="hoverTooltip.set(true)"
|
|
331
|
+
(mouseleave)="hoverTooltip.set(false)"
|
|
332
|
+
>{{ label() }}</div>
|
|
333
|
+
|
|
334
|
+
<!-- Named like the sibling pickers' popups: a screen reader entering the list hears what
|
|
335
|
+
it is for. -->
|
|
336
|
+
<ul
|
|
337
|
+
#listEl
|
|
338
|
+
class="link-picker-list"
|
|
339
|
+
[id]="listId"
|
|
340
|
+
[attr.aria-label]="label() || null"
|
|
341
|
+
[attr.hidden]="open() ? null : ''"
|
|
342
|
+
(keydown)="onListKeydown($event)"
|
|
343
|
+
>
|
|
344
|
+
@for (link of links(); track idOf(link)) {
|
|
345
|
+
<li class="link-picker-list-item">
|
|
346
|
+
<!-- A real link, not role="menuitem": these ARE navigation. -->
|
|
347
|
+
<a
|
|
348
|
+
class="link-picker-link"
|
|
349
|
+
[attr.data-link-id]="idOf(link)"
|
|
350
|
+
[attr.href]="link.href"
|
|
351
|
+
[attr.aria-current]="link.current ? 'page' : null"
|
|
352
|
+
[attr.target]="link.newTab ? '_blank' : null"
|
|
353
|
+
[attr.rel]="link.newTab ? 'noopener noreferrer' : null"
|
|
354
|
+
(click)="onLinkClick($event, link)"
|
|
355
|
+
>{{ link.label }}</a
|
|
356
|
+
>
|
|
357
|
+
</li>
|
|
358
|
+
}
|
|
359
|
+
</ul>
|
|
360
|
+
</div>
|
|
361
|
+
`,
|
|
362
|
+
isInline: true,
|
|
363
|
+
dependencies: [{
|
|
364
|
+
kind: "directive",
|
|
365
|
+
type: NgTemplateOutlet,
|
|
366
|
+
selector: "[ngTemplateOutlet]",
|
|
367
|
+
inputs: [
|
|
368
|
+
"ngTemplateOutletContext",
|
|
369
|
+
"ngTemplateOutlet",
|
|
370
|
+
"ngTemplateOutletInjector"
|
|
371
|
+
]
|
|
372
|
+
}, {
|
|
373
|
+
kind: "component",
|
|
374
|
+
type: IconButton,
|
|
375
|
+
selector: "lily-icon-button",
|
|
376
|
+
inputs: [
|
|
377
|
+
"label",
|
|
378
|
+
"className",
|
|
379
|
+
"baseClass",
|
|
380
|
+
"ariaHaspopup",
|
|
381
|
+
"ariaExpanded",
|
|
382
|
+
"ariaControls",
|
|
383
|
+
"tabIndex",
|
|
384
|
+
"disabled"
|
|
385
|
+
]
|
|
386
|
+
}],
|
|
387
|
+
changeDetection: i0.ChangeDetectionStrategy.OnPush
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
392
|
+
minVersion: "12.0.0",
|
|
393
|
+
version: "22.2.1",
|
|
394
|
+
ngImport: i0,
|
|
395
|
+
type: LinkPicker,
|
|
396
|
+
decorators: [{
|
|
397
|
+
type: Component,
|
|
398
|
+
args: [{
|
|
399
|
+
selector: "lily-link-picker",
|
|
400
|
+
standalone: true,
|
|
401
|
+
imports: [NgTemplateOutlet, IconButton],
|
|
402
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
403
|
+
host: { "(document:click)": "onDocumentClick($event)" },
|
|
404
|
+
template: `
|
|
405
|
+
<div
|
|
406
|
+
#rootEl
|
|
407
|
+
class="link-picker {{ className() }}"
|
|
408
|
+
(focusout)="onRootFocusOut($event)"
|
|
409
|
+
>
|
|
410
|
+
<lily-icon-button
|
|
411
|
+
#buttonEl
|
|
412
|
+
[label]="label()"
|
|
413
|
+
baseClass="link-picker-button"
|
|
414
|
+
[ariaExpanded]="open()"
|
|
415
|
+
[ariaControls]="listId"
|
|
416
|
+
(click)="onButtonClick(); hoverButton.set(false)"
|
|
417
|
+
(mouseenter)="onTooltipButtonEnter()"
|
|
418
|
+
(mouseleave)="hoverButton.set(false)"
|
|
419
|
+
(focusin)="onTooltipButtonFocusIn()"
|
|
420
|
+
(focusout)="onTooltipButtonFocusOut()"
|
|
421
|
+
(keydown)="onButtonKeydown($event); onTooltipKeydown($event)"
|
|
422
|
+
>
|
|
423
|
+
@if (iconTemplate(); as tpl) {
|
|
424
|
+
<ng-container
|
|
425
|
+
[ngTemplateOutlet]="tpl"
|
|
426
|
+
[ngTemplateOutletContext]="childContext()"
|
|
427
|
+
/>
|
|
428
|
+
} @else {
|
|
429
|
+
<svg
|
|
430
|
+
class="link-picker-icon"
|
|
431
|
+
viewBox="0 0 16 16"
|
|
432
|
+
width="1.05rem"
|
|
433
|
+
height="1.05rem"
|
|
434
|
+
aria-hidden="true"
|
|
435
|
+
fill="none"
|
|
436
|
+
stroke="currentColor"
|
|
437
|
+
stroke-width="1.6"
|
|
438
|
+
stroke-linecap="round"
|
|
439
|
+
stroke-linejoin="round"
|
|
440
|
+
>
|
|
441
|
+
<path d="M2 7.5 8 2.5l6 5" />
|
|
442
|
+
<path d="M3.5 6.5v7h9v-7" />
|
|
443
|
+
<path d="M6.5 13.5V10h3v3.5" />
|
|
444
|
+
</svg>
|
|
445
|
+
}
|
|
446
|
+
</lily-icon-button>
|
|
447
|
+
|
|
448
|
+
<!-- Purely visual: the same text is already the button's aria-label, so it is not wired
|
|
449
|
+
with aria-describedby (that would announce the name twice). -->
|
|
450
|
+
<div
|
|
451
|
+
class="link-picker-tooltip"
|
|
452
|
+
role="tooltip"
|
|
453
|
+
[id]="tooltipId"
|
|
454
|
+
[attr.hidden]="tooltipVisible() ? null : ''"
|
|
455
|
+
(mouseenter)="hoverTooltip.set(true)"
|
|
456
|
+
(mouseleave)="hoverTooltip.set(false)"
|
|
457
|
+
>{{ label() }}</div>
|
|
458
|
+
|
|
459
|
+
<!-- Named like the sibling pickers' popups: a screen reader entering the list hears what
|
|
460
|
+
it is for. -->
|
|
461
|
+
<ul
|
|
462
|
+
#listEl
|
|
463
|
+
class="link-picker-list"
|
|
464
|
+
[id]="listId"
|
|
465
|
+
[attr.aria-label]="label() || null"
|
|
466
|
+
[attr.hidden]="open() ? null : ''"
|
|
467
|
+
(keydown)="onListKeydown($event)"
|
|
468
|
+
>
|
|
469
|
+
@for (link of links(); track idOf(link)) {
|
|
470
|
+
<li class="link-picker-list-item">
|
|
471
|
+
<!-- A real link, not role="menuitem": these ARE navigation. -->
|
|
472
|
+
<a
|
|
473
|
+
class="link-picker-link"
|
|
474
|
+
[attr.data-link-id]="idOf(link)"
|
|
475
|
+
[attr.href]="link.href"
|
|
476
|
+
[attr.aria-current]="link.current ? 'page' : null"
|
|
477
|
+
[attr.target]="link.newTab ? '_blank' : null"
|
|
478
|
+
[attr.rel]="link.newTab ? 'noopener noreferrer' : null"
|
|
479
|
+
(click)="onLinkClick($event, link)"
|
|
480
|
+
>{{ link.label }}</a
|
|
481
|
+
>
|
|
482
|
+
</li>
|
|
483
|
+
}
|
|
484
|
+
</ul>
|
|
485
|
+
</div>
|
|
486
|
+
`
|
|
487
|
+
}]
|
|
488
|
+
}],
|
|
489
|
+
propDecorators: {
|
|
490
|
+
label: [{
|
|
491
|
+
type: i0.Input,
|
|
492
|
+
args: [{
|
|
493
|
+
isSignal: true,
|
|
494
|
+
alias: "label",
|
|
495
|
+
required: true
|
|
496
|
+
}]
|
|
497
|
+
}],
|
|
498
|
+
links: [{
|
|
499
|
+
type: i0.Input,
|
|
500
|
+
args: [{
|
|
501
|
+
isSignal: true,
|
|
502
|
+
alias: "links",
|
|
503
|
+
required: true
|
|
504
|
+
}]
|
|
505
|
+
}],
|
|
506
|
+
navigate: [{
|
|
507
|
+
type: i0.Input,
|
|
508
|
+
args: [{
|
|
509
|
+
isSignal: true,
|
|
510
|
+
alias: "navigate",
|
|
511
|
+
required: false
|
|
512
|
+
}]
|
|
513
|
+
}],
|
|
514
|
+
className: [{
|
|
515
|
+
type: i0.Input,
|
|
516
|
+
args: [{
|
|
517
|
+
isSignal: true,
|
|
518
|
+
alias: "className",
|
|
519
|
+
required: false
|
|
520
|
+
}]
|
|
521
|
+
}],
|
|
522
|
+
navigated: [{
|
|
523
|
+
type: i0.Output,
|
|
524
|
+
args: ["navigated"]
|
|
525
|
+
}],
|
|
526
|
+
iconTemplate: [{
|
|
527
|
+
type: i0.ContentChild,
|
|
528
|
+
args: [i0.forwardRef(() => TemplateRef), { isSignal: true }]
|
|
529
|
+
}],
|
|
530
|
+
rootRef: [{
|
|
531
|
+
type: i0.ViewChild,
|
|
532
|
+
args: ["rootEl", { isSignal: true }]
|
|
533
|
+
}],
|
|
534
|
+
buttonRef: [{
|
|
535
|
+
type: i0.ViewChild,
|
|
536
|
+
args: ["buttonEl", { isSignal: true }]
|
|
537
|
+
}],
|
|
538
|
+
listRef: [{
|
|
539
|
+
type: i0.ViewChild,
|
|
540
|
+
args: ["listEl", { isSignal: true }]
|
|
541
|
+
}]
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
export { LinkPicker, LinkPickerIcon, linkId, nextLinkPickerId };
|
|
545
|
+
|
|
546
|
+
//# sourceMappingURL=lilydesignsystem-angular-link-picker.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lilydesignsystem-angular-link-picker.mjs","names":[],"sources":["../../link-picker.component.ts"],"sourcesContent":["import { NgTemplateOutlet } from \"@angular/common\";\nimport {\n ChangeDetectionStrategy,\n Component,\n Directive,\n ElementRef,\n TemplateRef,\n computed,\n contentChild,\n effect,\n input,\n output,\n signal,\n viewChild,\n} from \"@angular/core\";\n// Only the trigger button composes a headless primitive. The list below is real `<a>` navigation\n// with a roving-focus pattern of its own — not an ARIA listbox or menu: role=\"menuitem\" would strip\n// middle-click, open-in-new-tab and copy-link-address from ordinary links. See spec/index.md §3.\nimport { IconButton } from \"@lilydesignsystem/angular-headless\";\n\n/**\n * One destination in the list. The app defines them all: this package ships no routes and no\n * English — `label` is the consumer's text.\n */\nexport type LinkItem = {\n /** Stable identifier, passed back on the `navigated` output. Defaults to `href`. */\n id?: string;\n /** Visible link text. Consumer-supplied, so it localises. */\n label: string;\n /** Where the link goes: a route (\"/about/\") or a full URL. */\n href: string;\n /** Marks this link as the current page (`aria-current=\"page\"`). */\n current?: boolean;\n /** Open in a new tab (`target=\"_blank\"` with `rel=\"noopener noreferrer\"`). */\n newTab?: boolean;\n};\n\n/** Context passed to a custom icon `<ng-template>` (the button icon). */\nexport type ChildArgs = {\n /** Is the list open? */\n open: boolean;\n};\n\n/** Payload of the `navigated` output. */\nexport type LinkNavigatedEvent = {\n /** The chosen link's `id` (its `href` when it has none). */\n id: string;\n /** The chosen link's `href`. */\n href: string;\n};\n\n/** The id a link reports: its explicit `id`, else its `href`. */\nexport function linkId(link: LinkItem): string {\n return link.id ?? link.href;\n}\n\nlet uid = 0;\n/** Stable per-instance id prefix; SSR-safe (no Math.random / Date.now). */\nexport function nextLinkPickerId(): string {\n uid += 1;\n return `link-picker-${uid}`;\n}\n\n/**\n * Optional marker for the projected icon template. Gives consumers typed `let-` variables:\n *\n * ```html\n * <lily-link-picker label=\"Pages\" [links]=\"links\">\n * <ng-template lilyLinkPickerIcon let-args>{{ args.open ? \"×\" : \"☰\" }}</ng-template>\n * </lily-link-picker>\n * ```\n */\n@Directive({\n selector: \"ng-template[lilyLinkPickerIcon]\",\n standalone: true,\n})\nexport class LinkPickerIcon {\n static ngTemplateContextGuard(\n _dir: LinkPickerIcon,\n _ctx: unknown,\n ): _ctx is ChildArgs & { $implicit: ChildArgs } {\n return true;\n }\n}\n\n/**\n * LinkPicker — a headless page-links control.\n *\n * A single-icon button (a bundled home SVG) that opens a disclosure list of page links the app\n * defines. It applies nothing to the document and persists nothing. See `spec/index.md`.\n */\n@Component({\n selector: \"lily-link-picker\",\n standalone: true,\n imports: [NgTemplateOutlet, IconButton],\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n \"(document:click)\": \"onDocumentClick($event)\",\n },\n template: `\n <div\n #rootEl\n class=\"link-picker {{ className() }}\"\n (focusout)=\"onRootFocusOut($event)\"\n >\n <lily-icon-button\n #buttonEl\n [label]=\"label()\"\n baseClass=\"link-picker-button\"\n [ariaExpanded]=\"open()\"\n [ariaControls]=\"listId\"\n (click)=\"onButtonClick(); 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=\"link-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=\"M2 7.5 8 2.5l6 5\" />\n <path d=\"M3.5 6.5v7h9v-7\" />\n <path d=\"M6.5 13.5V10h3v3.5\" />\n </svg>\n }\n </lily-icon-button>\n\n <!-- Purely visual: the same text is already the button's aria-label, so it is not wired\n with aria-describedby (that would announce the name twice). -->\n <div\n class=\"link-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 <!-- Named like the sibling pickers' popups: a screen reader entering the list hears what\n it is for. -->\n <ul\n #listEl\n class=\"link-picker-list\"\n [id]=\"listId\"\n [attr.aria-label]=\"label() || null\"\n [attr.hidden]=\"open() ? null : ''\"\n (keydown)=\"onListKeydown($event)\"\n >\n @for (link of links(); track idOf(link)) {\n <li class=\"link-picker-list-item\">\n <!-- A real link, not role=\"menuitem\": these ARE navigation. -->\n <a\n class=\"link-picker-link\"\n [attr.data-link-id]=\"idOf(link)\"\n [attr.href]=\"link.href\"\n [attr.aria-current]=\"link.current ? 'page' : null\"\n [attr.target]=\"link.newTab ? '_blank' : null\"\n [attr.rel]=\"link.newTab ? 'noopener noreferrer' : null\"\n (click)=\"onLinkClick($event, link)\"\n >{{ link.label }}</a\n >\n </li>\n }\n </ul>\n </div>\n `,\n})\nexport class LinkPicker {\n /** Accessible name for the button and the list. */\n readonly label = input.required<string>();\n /** The page links to offer. Defined by the app. */\n readonly links = input.required<LinkItem[]>();\n /**\n * Client-side navigation hook, e.g. a router's `navigateByUrl`. When given, a plain left click\n * on a link calls `navigate(href)` instead of letting the browser load the page; modified\n * clicks (Ctrl/Cmd/Shift/Alt, middle button) and `newTab` links stay native.\n */\n readonly navigate = input<((href: string) => void) | undefined>(undefined);\n /** Extra CSS class on the root div. */\n readonly className = input<string>(\"\");\n\n /** Fires after a link is chosen. */\n readonly navigated = output<LinkNavigatedEvent>();\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 // A template-ref-variable on a component tag resolves to the component INSTANCE, which is what\n // is needed to call the headless component's own public `focus()` method.\n private readonly buttonRef = viewChild.required<IconButton>(\"buttonEl\");\n private readonly listRef =\n viewChild.required<ElementRef<HTMLUListElement>>(\"listEl\");\n\n private readonly baseId = nextLinkPickerId();\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 itself (hoverable, WCAG\n // 1.4.13) or while the button has keyboard focus; Escape dismisses it without moving focus;\n // never shown while 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 Chromium, and the tooltip\n // 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 wherever focus is. The\n // listener exists only while visible; the effect cleanup removes it on hide and on destroy.\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\n protected readonly childContext = computed(() => {\n const args: ChildArgs = { open: this.open() };\n return { $implicit: args, ...args };\n });\n\n protected idOf(link: LinkItem): string {\n return linkId(link);\n }\n\n /** Every focusable link in the list, in DOM order. */\n private items(): HTMLElement[] {\n return Array.from(\n this.listRef().nativeElement.querySelectorAll<HTMLElement>(\".link-picker-link\"),\n );\n }\n\n // ---------------------------------------------------------------\n // Open / close\n // ---------------------------------------------------------------\n\n /** Open the list, focusing the first link (or the last, if `focusLast`). */\n openList(focusLast = false): void {\n this.open.set(true);\n // Deferred so the `hidden` attribute is gone before focus is moved.\n queueMicrotask(() => {\n const all = this.items();\n (focusLast ? all[all.length - 1] : all[0])?.focus({ preventScroll: true });\n });\n }\n\n /** Close the list; `refocus` returns focus to the trigger. */\n closeList(refocus = true): void {\n if (!this.open()) return;\n this.open.set(false);\n if (refocus) queueMicrotask(() => this.buttonRef().focus({ preventScroll: true }));\n }\n\n protected onButtonClick(): void {\n if (this.open()) this.closeList();\n else this.openList();\n }\n\n protected onLinkClick(event: MouseEvent, link: LinkItem): void {\n this.navigated.emit({ id: linkId(link), href: link.href });\n const go = this.navigate();\n const modified =\n event.ctrlKey || event.metaKey || event.shiftKey || event.altKey || event.button !== 0;\n if (go && !link.newTab && !modified && !event.defaultPrevented) {\n event.preventDefault();\n go(link.href);\n }\n this.closeList();\n }\n\n // ---------------------------------------------------------------\n // Keyboard\n // ---------------------------------------------------------------\n\n protected onButtonKeydown(event: KeyboardEvent): void {\n // Enter and Space already produce a click; only the arrows need handling here.\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n if (!this.open()) this.openList();\n else this.items()[0]?.focus({ preventScroll: true });\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n if (!this.open()) this.openList(true);\n else {\n const all = this.items();\n all[all.length - 1]?.focus({ preventScroll: true });\n }\n }\n }\n\n private moveFocus(delta: number): void {\n const all = this.items();\n if (all.length === 0) return;\n const i = all.indexOf(document.activeElement as HTMLElement);\n // Clamp rather than wrap.\n const next = Math.min(Math.max((i < 0 ? 0 : i) + delta, 0), all.length - 1);\n all[next]?.focus({ preventScroll: true });\n }\n\n protected onListKeydown(event: KeyboardEvent): void {\n switch (event.key) {\n case \"ArrowDown\":\n event.preventDefault();\n this.moveFocus(1);\n break;\n case \"ArrowUp\":\n event.preventDefault();\n this.moveFocus(-1);\n break;\n case \"Home\":\n event.preventDefault();\n this.items()[0]?.focus({ preventScroll: true });\n break;\n case \"End\": {\n event.preventDefault();\n const all = this.items();\n all[all.length - 1]?.focus({ preventScroll: true });\n break;\n }\n case \"Escape\":\n event.preventDefault();\n this.closeList();\n break;\n case \"Tab\":\n // Focus goes to the button FIRST, without cancelling the key: hiding the list while a\n // link has focus drops focus to <body>, and the default Tab would restart from the top.\n this.buttonRef().focus?.({ preventScroll: true });\n this.closeList(false);\n break;\n default:\n break;\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"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lilydesignsystem/angular-link-picker",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"engines": {
|
|
5
|
+
"node": "=26"
|
|
6
|
+
},
|
|
7
|
+
"description": "Lily Design System Angular link picker: an icon button (a home icon) opening a disclosure of app-defined page links. Headless, SSR-safe, no CSS.",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@lilydesignsystem/angular-headless": "^0.2.0",
|
|
10
|
+
"tslib": "^2.3.0"
|
|
11
|
+
},
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"@angular/core": ">=20.0.0 <23.0.0",
|
|
14
|
+
"@angular/common": ">=20.0.0 <23.0.0"
|
|
15
|
+
},
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"keywords": [
|
|
18
|
+
"lily",
|
|
19
|
+
"design",
|
|
20
|
+
"system",
|
|
21
|
+
"angular",
|
|
22
|
+
"link",
|
|
23
|
+
"navigation",
|
|
24
|
+
"home",
|
|
25
|
+
"picker"
|
|
26
|
+
],
|
|
27
|
+
"author": "Joel Parker Henderson <joel@joelparkerhenderson.com>",
|
|
28
|
+
"license": "MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/LilyDesignSystem/lily-design-system-angular-helpers.git",
|
|
32
|
+
"directory": "lily-design-system-angular-link-picker"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://lilydesignsystem.com/",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/LilyDesignSystem/lily-design-system/issues"
|
|
37
|
+
},
|
|
38
|
+
"module": "fesm2022/lilydesignsystem-angular-link-picker.mjs",
|
|
39
|
+
"typings": "types/lilydesignsystem-angular-link-picker.d.ts",
|
|
40
|
+
"exports": {
|
|
41
|
+
"./package.json": {
|
|
42
|
+
"default": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
".": {
|
|
45
|
+
"types": "./types/lilydesignsystem-angular-link-picker.d.ts",
|
|
46
|
+
"default": "./fesm2022/lilydesignsystem-angular-link-picker.mjs"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"type": "module"
|
|
50
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
import { TemplateRef } from "@angular/core";
|
|
3
|
+
/**
|
|
4
|
+
* One destination in the list. The app defines them all: this package ships no routes and no
|
|
5
|
+
* English — `label` is the consumer's text.
|
|
6
|
+
*/
|
|
7
|
+
type LinkItem = {
|
|
8
|
+
/** Stable identifier, passed back on the `navigated` output. Defaults to `href`. */
|
|
9
|
+
id?: string;
|
|
10
|
+
/** Visible link text. Consumer-supplied, so it localises. */
|
|
11
|
+
label: string;
|
|
12
|
+
/** Where the link goes: a route ("/about/") or a full URL. */
|
|
13
|
+
href: string;
|
|
14
|
+
/** Marks this link as the current page (`aria-current="page"`). */
|
|
15
|
+
current?: boolean;
|
|
16
|
+
/** Open in a new tab (`target="_blank"` with `rel="noopener noreferrer"`). */
|
|
17
|
+
newTab?: boolean;
|
|
18
|
+
};
|
|
19
|
+
/** Context passed to a custom icon `<ng-template>` (the button icon). */
|
|
20
|
+
type ChildArgs = {
|
|
21
|
+
/** Is the list open? */
|
|
22
|
+
open: boolean;
|
|
23
|
+
};
|
|
24
|
+
/** Payload of the `navigated` output. */
|
|
25
|
+
type LinkNavigatedEvent = {
|
|
26
|
+
/** The chosen link's `id` (its `href` when it has none). */
|
|
27
|
+
id: string;
|
|
28
|
+
/** The chosen link's `href`. */
|
|
29
|
+
href: string;
|
|
30
|
+
};
|
|
31
|
+
/** The id a link reports: its explicit `id`, else its `href`. */
|
|
32
|
+
export declare function linkId(link: LinkItem): string;
|
|
33
|
+
/** Stable per-instance id prefix; SSR-safe (no Math.random / Date.now). */
|
|
34
|
+
export declare function nextLinkPickerId(): string;
|
|
35
|
+
/**
|
|
36
|
+
* Optional marker for the projected icon template. Gives consumers typed `let-` variables:
|
|
37
|
+
*
|
|
38
|
+
* ```html
|
|
39
|
+
* <lily-link-picker label="Pages" [links]="links">
|
|
40
|
+
* <ng-template lilyLinkPickerIcon let-args>{{ args.open ? "×" : "☰" }}</ng-template>
|
|
41
|
+
* </lily-link-picker>
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare class LinkPickerIcon {
|
|
45
|
+
static ngTemplateContextGuard(_dir: LinkPickerIcon, _ctx: unknown): _ctx is ChildArgs & {
|
|
46
|
+
$implicit: ChildArgs;
|
|
47
|
+
};
|
|
48
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LinkPickerIcon, never>;
|
|
49
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<LinkPickerIcon, "ng-template[lilyLinkPickerIcon]", never, {}, {}, never, never, true, never>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* LinkPicker — a headless page-links control.
|
|
53
|
+
*
|
|
54
|
+
* A single-icon button (a bundled home SVG) that opens a disclosure list of page links the app
|
|
55
|
+
* defines. It applies nothing to the document and persists nothing. See `spec/index.md`.
|
|
56
|
+
*/
|
|
57
|
+
export declare class LinkPicker {
|
|
58
|
+
/** Accessible name for the button and the list. */
|
|
59
|
+
readonly label: import("@angular/core").InputSignal<string>;
|
|
60
|
+
/** The page links to offer. Defined by the app. */
|
|
61
|
+
readonly links: import("@angular/core").InputSignal<LinkItem[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Client-side navigation hook, e.g. a router's `navigateByUrl`. When given, a plain left click
|
|
64
|
+
* on a link calls `navigate(href)` instead of letting the browser load the page; modified
|
|
65
|
+
* clicks (Ctrl/Cmd/Shift/Alt, middle button) and `newTab` links stay native.
|
|
66
|
+
*/
|
|
67
|
+
readonly navigate: import("@angular/core").InputSignal<((href: string) => void) | undefined>;
|
|
68
|
+
/** Extra CSS class on the root div. */
|
|
69
|
+
readonly className: import("@angular/core").InputSignal<string>;
|
|
70
|
+
/** Fires after a link is chosen. */
|
|
71
|
+
readonly navigated: import("@angular/core").OutputEmitterRef<LinkNavigatedEvent>;
|
|
72
|
+
/** Projected icon template; replaces the default icon when supplied. */
|
|
73
|
+
protected readonly iconTemplate: import("@angular/core").Signal<TemplateRef<any> | undefined>;
|
|
74
|
+
private readonly rootRef;
|
|
75
|
+
private readonly buttonRef;
|
|
76
|
+
private readonly listRef;
|
|
77
|
+
private readonly baseId;
|
|
78
|
+
protected readonly listId: string;
|
|
79
|
+
protected readonly open: import("@angular/core").WritableSignal<boolean>;
|
|
80
|
+
protected readonly tooltipId: string;
|
|
81
|
+
protected readonly hoverButton: import("@angular/core").WritableSignal<boolean>;
|
|
82
|
+
protected readonly hoverTooltip: import("@angular/core").WritableSignal<boolean>;
|
|
83
|
+
protected readonly focusButton: import("@angular/core").WritableSignal<boolean>;
|
|
84
|
+
protected readonly dismissed: import("@angular/core").WritableSignal<boolean>;
|
|
85
|
+
protected readonly tooltipVisible: import("@angular/core").Signal<boolean>;
|
|
86
|
+
protected onTooltipButtonEnter(): void;
|
|
87
|
+
protected onTooltipButtonFocusIn(): void;
|
|
88
|
+
protected onTooltipButtonFocusOut(): void;
|
|
89
|
+
protected onTooltipKeydown(event: KeyboardEvent): void;
|
|
90
|
+
private readonly tooltipEscapeListener;
|
|
91
|
+
protected readonly childContext: import("@angular/core").Signal<{
|
|
92
|
+
/** Is the list open? */
|
|
93
|
+
open: boolean;
|
|
94
|
+
$implicit: ChildArgs;
|
|
95
|
+
}>;
|
|
96
|
+
protected idOf(link: LinkItem): string;
|
|
97
|
+
/** Every focusable link in the list, in DOM order. */
|
|
98
|
+
private items;
|
|
99
|
+
/** Open the list, focusing the first link (or the last, if `focusLast`). */
|
|
100
|
+
openList(focusLast?: boolean): void;
|
|
101
|
+
/** Close the list; `refocus` returns focus to the trigger. */
|
|
102
|
+
closeList(refocus?: boolean): void;
|
|
103
|
+
protected onButtonClick(): void;
|
|
104
|
+
protected onLinkClick(event: MouseEvent, link: LinkItem): void;
|
|
105
|
+
protected onButtonKeydown(event: KeyboardEvent): void;
|
|
106
|
+
private moveFocus;
|
|
107
|
+
protected onListKeydown(event: KeyboardEvent): void;
|
|
108
|
+
protected onRootFocusOut(event: FocusEvent): void;
|
|
109
|
+
protected onDocumentClick(event: Event): void;
|
|
110
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LinkPicker, never>;
|
|
111
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<LinkPicker, "lily-link-picker", never, {
|
|
112
|
+
"label": {
|
|
113
|
+
"alias": "label";
|
|
114
|
+
"required": true;
|
|
115
|
+
"isSignal": true;
|
|
116
|
+
};
|
|
117
|
+
"links": {
|
|
118
|
+
"alias": "links";
|
|
119
|
+
"required": true;
|
|
120
|
+
"isSignal": true;
|
|
121
|
+
};
|
|
122
|
+
"navigate": {
|
|
123
|
+
"alias": "navigate";
|
|
124
|
+
"required": false;
|
|
125
|
+
"isSignal": true;
|
|
126
|
+
};
|
|
127
|
+
"className": {
|
|
128
|
+
"alias": "className";
|
|
129
|
+
"required": false;
|
|
130
|
+
"isSignal": true;
|
|
131
|
+
};
|
|
132
|
+
}, {
|
|
133
|
+
"navigated": "navigated";
|
|
134
|
+
}, ["iconTemplate"], never, true, never>;
|
|
135
|
+
}
|
|
136
|
+
export type { ChildArgs, LinkItem, LinkNavigatedEvent };
|
|
137
|
+
//# sourceMappingURL=lilydesignsystem-angular-link-picker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lilydesignsystem-angular-link-picker.d.ts","names":[],"sources":["../../link-picker.component.ts"],"mappings":";;;;;;KAwBY;;EAEV;;EAEA;;EAEA;;EAEA;;EAEA;;;KAIU;;EAEV;;;KAIU;;EAEV;;EAEA;;;wBAIc,OAAO,MAAM;;wBAMb;;;;;;;;;;qBAkBH;SACJ,uBACL,MAAM,gBACN,gBACC,QAAQ;IAAc,WAAW;;SAJzB,MAAA,GAAA,qBAAA;SAAA,MAAA,GAAA,uBAAA;;;;;;;;qBA2GA;;WAEF,+BAAK;;WAEL,+BAAK,YAAA;;;;;;WAML,kCAAQ,cAAA;;WAER,mCAAS;;WAGT,mCAAS,iBAAA;;qBAGC,sCAAY,OAAA;mBAEd;mBAIA;mBACA;mBAGA;qBACE;qBAEA,8BAAI;qBAKJ;qBACA,qCAAW;qBACX,sCAAY;qBACZ,qCAAW;qBACX,mCAAS;qBACT,wCAAc;YAOvB;YAKA;YAYA;YAKA,iBAAiB,OAAO;mBAMjB;qBASE,sCAAY;;IApOzB;;;YAyOI,KAAK,MAAM;;UAKb;;EAWR,SAAS;;EAUT,UAAU;YAMA;YAKA,YAAY,OAAO,YAAY,MAAM;YAgBrC,gBAAgB,OAAO;UAgBzB;YASE,cAAc,OAAO;YAmCrB,eAAe,OAAO;YAMtB,gBAAgB,OAAO;SAjNtB,MAAA,GAAA,qBAAA;SAAA,MAAA,GAAA,uBAAA;IAAU;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;IAAA;MAAA;MAAA;MAAA;;;IAAA"}
|